Fixed A11Y issues by replacing react-dropdown-aria by react-select component

Fixed: ARIA input fields must have an accessible name
Fixed: Ensure interactive controls are not nested
Coded formatted with prettier
Files changed:
dropdown.ts
styled.elements.ts
PayloadSamples.tsx
SchemaDefinition.tsx
DiscriminatorDropdown.tsx
ResponseDetails.tsx
MediaTypeSamples.tsx
Parameters.tsx
MediaTypesSwitch.tsx
DropdownOrLabel.tsx
CallbackSamples.tsx
GenericChildrenSwitcher.tsx
This commit is contained in:
Pedro Novales 2021-10-25 12:31:18 -07:00
parent ebbd256b1f
commit 42f80fc642
96 changed files with 745 additions and 21490 deletions

View File

@ -45,7 +45,7 @@ const BUNDLES_DIR = dirname(require.resolve('redoc'));
YargsParser.command( YargsParser.command(
'serve <spec>', 'serve <spec>',
'start the server', 'start the server',
yargs => { (yargs) => {
yargs.positional('spec', { yargs.positional('spec', {
describe: 'path or URL to your spec', describe: 'path or URL to your spec',
}); });
@ -81,7 +81,7 @@ YargsParser.command(
yargs.demandOption('spec'); yargs.demandOption('spec');
return yargs; return yargs;
}, },
async argv => { async (argv) => {
const config: Options = { const config: Options = {
ssr: argv.ssr as boolean, ssr: argv.ssr as boolean,
title: argv.title as string, title: argv.title as string,
@ -102,7 +102,7 @@ YargsParser.command(
.command( .command(
'bundle <spec>', 'bundle <spec>',
'bundle spec into zero-dependency HTML-file', 'bundle spec into zero-dependency HTML-file',
yargs => { (yargs) => {
yargs.positional('spec', { yargs.positional('spec', {
describe: 'path or URL to your spec', describe: 'path or URL to your spec',
}); });
@ -213,7 +213,7 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
const watcher = watch(pathToSpecDirectory, watchOptions); const watcher = watch(pathToSpecDirectory, watchOptions);
const log = console.log.bind(console); const log = console.log.bind(console);
const handlePath = async _path => { const handlePath = async (_path) => {
try { try {
spec = await loadAndBundleSpec(resolve(pathToSpec)); spec = await loadAndBundleSpec(resolve(pathToSpec));
pageHTML = await getPageHTML(spec, pathToSpec, options); pageHTML = await getPageHTML(spec, pathToSpec, options);
@ -224,18 +224,18 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
}; };
watcher watcher
.on('change', async path => { .on('change', async (path) => {
log(`${path} changed, updating docs`); log(`${path} changed, updating docs`);
handlePath(path); handlePath(path);
}) })
.on('add', async path => { .on('add', async (path) => {
log(`File ${path} added, updating docs`); log(`File ${path} added, updating docs`);
handlePath(path); handlePath(path);
}) })
.on('addDir', path => { .on('addDir', (path) => {
log(`↗ Directory ${path} added. Files in here will trigger reload.`); log(`↗ Directory ${path} added. Files in here will trigger reload.`);
}) })
.on('error', error => console.error(`Watcher error: ${error}`)) .on('error', (error) => console.error(`Watcher error: ${error}`))
.on('ready', () => log(`👀 Watching ${pathToSpecDirectory} for changes...`)); .on('ready', () => log(`👀 Watching ${pathToSpecDirectory} for changes...`));
} }
} }
@ -364,7 +364,7 @@ function escapeClosingScriptTag(str) {
// see http://www.thespanner.co.uk/2011/07/25/the-json-specification-is-now-wrong/ // see http://www.thespanner.co.uk/2011/07/25/the-json-specification-is-now-wrong/
function escapeUnicode(str) { function escapeUnicode(str) {
return str.replace(/\u2028|\u2029/g, m => '\\u202' + (m === '\u2028' ? '8' : '9')); return str.replace(/\u2028|\u2029/g, (m) => '\\u202' + (m === '\u2028' ? '8' : '9'));
} }
function handleError(error: Error) { function handleError(error: Error) {

21207
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -148,7 +148,6 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.14.0", "@babel/runtime": "^7.14.0",
"@redocly/openapi-core": "^1.0.0-beta.54", "@redocly/openapi-core": "^1.0.0-beta.54",
"@redocly/react-dropdown-aria": "^2.0.11",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"decko": "^1.2.0", "decko": "^1.2.0",
"dompurify": "^2.2.8", "dompurify": "^2.2.8",
@ -165,6 +164,7 @@
"polished": "^4.1.3", "polished": "^4.1.3",
"prismjs": "^1.24.1", "prismjs": "^1.24.1",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react-select": "^5.1.0",
"react-tabs": "^3.2.2", "react-tabs": "^3.2.2",
"slugify": "~1.4.7", "slugify": "~1.4.7",
"stickyfill": "^1.1.1", "stickyfill": "^1.1.1",

View File

@ -1,113 +1,102 @@
import Dropdown from '@redocly/react-dropdown-aria'; import Dropdown from 'react-select';
import styled from '../styled-components'; import styled from '../styled-components';
export interface DropdownOption { export interface DropdownOption {
idx: number; label: string;
value: string; value: string;
} }
export interface DropdownProps { export interface DropdownProps {
options: DropdownOption[]; options: DropdownOption[];
value: string; value: DropdownOption;
onChange: (option: DropdownOption) => void; onChange: (val: DropdownOption) => void;
ariaLabel: string; ariaLabel: string;
} }
export const StyledDropdown = styled(Dropdown)` export const StyledDropdown = styled(Dropdown)`
&& { && {
box-sizing: border-box;
min-width: 100px;
outline: none;
display: inline-block; display: inline-block;
border-radius: 2px;
border: 1px solid rgba(38, 50, 56, 0.5);
vertical-align: bottom;
padding: 2px 0px 2px 6px;
position: relative; position: relative;
width: auto; width: auto;
background: white; min-width: 100px;
color: #263238; border-radius: 2px;
box-sizing: border-box;
vertical-align: bottom;
outline: none;
font-family: ${(props) => props.theme.typography.headings.fontFamily}; font-family: ${(props) => props.theme.typography.headings.fontFamily};
font-size: 0.929em; font-size: 0.929em;
line-height: 1.5em; line-height: 1.5em;
background: ${({ theme }) => theme.rightPanel.textColor};
color: #263238;
cursor: pointer; cursor: pointer;
transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease; transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
&:hover, &:hover,
&:focus-within { &:focus-within {
border: 1px solid ${(props) => props.theme.colors.primary.main}; border: 1px solid ${(props) => props.theme.colors.border.light};
color: ${(props) => props.theme.colors.primary.main};
box-shadow: 0px 0px 0px 1px ${(props) => props.theme.colors.primary.main}; box-shadow: 0px 0px 0px 1px ${(props) => props.theme.colors.primary.main};
color: ${(props) => props.theme.colors.primary.main};
} }
.dropdown-selector {
display: inline-flex; .react-dropdown__control {
min-height: 0;
border: 1px solid rgba(38, 50, 56, 0.5);
border-radius: 2px;
.react-dropdown__value-container {
padding: 4px 8px;
}
.react-dropdown__indicator-separator {
display: none;
}
.react-dropdown__indicator {
margin: 4px 8px 2px 0;
padding: 0; padding: 0;
height: auto;
padding-right: 20px;
position: relative;
margin-bottom: 5px;
}
.dropdown-selector-value {
font-family: ${(props) => props.theme.typography.headings.fontFamily};
position: relative;
font-size: 0.929em;
width: 100%;
line-height: 1;
vertical-align: middle;
color: #263238;
left: 0;
transition: color 0.25s ease, text-shadow 0.25s ease;
}
.dropdown-arrow {
position: absolute;
right: 3px;
top: 50%;
transform: translateY(-50%);
border-color: ${(props) => props.theme.colors.primary.main} transparent transparent; border-color: ${(props) => props.theme.colors.primary.main} transparent transparent;
border-style: solid; border-style: solid;
border-width: 0.35em 0.35em 0; border-width: 0.35em 0.35em 0;
width: 0;
svg { svg {
display: none; display: none;
} }
} }
.dropdown-selector-content {
position: absolute;
margin-top: 2px;
left: -2px;
right: 0;
z-index: 10;
min-width: 100px;
background: white;
border: 1px solid rgba(38, 50, 56, 0.2);
box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08);
max-height: 220px;
overflow: auto;
} }
.dropdown-option { .react-dropdown__menu {
font-size: 0.9em; margin: 4px 0 8px;
color: #263238; border-radius: 2px;
cursor: pointer; z-index: 3;
padding: 0.4em;
background-color: #ffffff;
&[aria-selected='true'] { .react-dropdown__menu-list {
background-color: rgba(0, 0, 0, 0.05); padding: 2px 0;
}
.react-dropdown__option {
padding: 6px 0px 6px 10px;
color: rgb(38, 50, 56);
background-color: ${({ theme }) => theme.rightPanel.textColor};
&:hover { &:hover {
background-color: rgba(38, 50, 56, 0.12); background-color: rgba(38, 50, 56, 0.12);
} }
} }
input {
cursor: pointer; .react-dropdown__option--is-selected {
height: 1px; background-color: rgba(0, 0, 0, 0.05);
background-color: transparent; }
.react-dropdown__option--is-focused {
background-color: rgba(38, 50, 56, 0.12);
}
}
} }
} }
`; `;
@ -115,19 +104,24 @@ export const StyledDropdown = styled(Dropdown)`
export const SimpleDropdown = styled(StyledDropdown)` export const SimpleDropdown = styled(StyledDropdown)`
&& { && {
margin-left: 10px; margin-left: 10px;
text-transform: none;
font-size: 0.969em;
font-size: 1em;
border: none; border: none;
padding: 0 1.2em 0 0;
font-size: 0.969em;
text-transform: none;
background: transparent; background: transparent;
.react-dropdown__control {
border: none;
.react-dropdown__indicator {
margin: 4px 8px 0px 0;
}
}
&:hover, &:hover,
&:focus-within { &:focus-within {
border: none; .react-dropdown__single-value {
box-shadow: none;
.dropdown-selector-value {
color: ${(props) => props.theme.colors.primary.main}; color: ${(props) => props.theme.colors.primary.main};
text-shadow: 0px 0px 0px ${(props) => props.theme.colors.primary.main}; text-shadow: 0px 0px 0px ${(props) => props.theme.colors.primary.main};
} }

View File

@ -5,11 +5,11 @@ export const PropertiesTableCaption = styled.caption`
text-align: right; text-align: right;
font-size: 0.9em; font-size: 0.9em;
font-weight: normal; font-weight: normal;
color: ${props => props.theme.colors.text.secondary}; color: ${(props) => props.theme.colors.text.secondary};
`; `;
export const PropertyCell = styled.td<{ kind?: string }>` export const PropertyCell = styled.td<{ kind?: string }>`
border-left: 1px solid ${props => props.theme.schema.linesColor}; border-left: 1px solid ${(props) => props.theme.schema.linesColor};
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
padding: 10px 10px 10px 0; padding: 10px 10px 10px 0;
@ -32,16 +32,16 @@ export const PropertyCell = styled.td<{ kind?: string }>`
to bottom, to bottom,
transparent 0%, transparent 0%,
transparent 22px, transparent 22px,
${props => props.theme.schema.linesColor} 22px, ${(props) => props.theme.schema.linesColor} 22px,
${props => props.theme.schema.linesColor} 100% ${(props) => props.theme.schema.linesColor} 100%
); );
} }
tr.last > & { tr.last > & {
background-image: linear-gradient( background-image: linear-gradient(
to bottom, to bottom,
${props => props.theme.schema.linesColor} 0%, ${(props) => props.theme.schema.linesColor} 0%,
${props => props.theme.schema.linesColor} 22px, ${(props) => props.theme.schema.linesColor} 22px,
transparent 22px, transparent 22px,
transparent 100% transparent 100%
); );
@ -101,8 +101,8 @@ export const PropertyDetailsCell = styled.td`
`; `;
export const PropertyBullet = styled.span` export const PropertyBullet = styled.span`
color: ${props => props.theme.schema.linesColor}; color: ${(props) => props.theme.schema.linesColor};
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${(props) => props.theme.typography.code.fontFamily};
margin-right: 10px; margin-right: 10px;
&::before { &::before {
@ -111,7 +111,7 @@ export const PropertyBullet = styled.span`
vertical-align: middle; vertical-align: middle;
width: 10px; width: 10px;
height: 1px; height: 1px;
background: ${props => props.theme.schema.linesColor}; background: ${(props) => props.theme.schema.linesColor};
} }
&::after { &::after {
@ -119,7 +119,7 @@ export const PropertyBullet = styled.span`
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
width: 1px; width: 1px;
background: ${props => props.theme.schema.linesColor}; background: ${(props) => props.theme.schema.linesColor};
height: 7px; height: 7px;
} }
`; `;
@ -131,7 +131,7 @@ export const InnerPropertiesWrap = styled.div`
export const PropertiesTable = styled.table` export const PropertiesTable = styled.table`
border-collapse: separate; border-collapse: separate;
border-radius: 3px; border-radius: 3px;
font-size: ${props => props.theme.typography.fontSize}; font-size: ${(props) => props.theme.typography.fontSize};
border-spacing: 0; border-spacing: 0;
width: 100%; width: 100%;

View File

@ -10,10 +10,10 @@ export const ClickablePropertyNameCell = styled(PropertyNameCell)`
border: 0; border: 0;
outline: 0; outline: 0;
font-size: 13px; font-size: 13px;
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${(props) => props.theme.typography.code.fontFamily};
cursor: pointer; cursor: pointer;
padding: 0; padding: 0;
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
&:focus { &:focus {
font-weight: ${({ theme }) => theme.typography.fontWeightBold}; font-weight: ${({ theme }) => theme.typography.fontWeightBold};
} }
@ -34,23 +34,23 @@ export const FieldLabel = styled.span`
`; `;
export const TypePrefix = styled(FieldLabel)` export const TypePrefix = styled(FieldLabel)`
color: ${props => transparentize(0.1, props.theme.schema.typeNameColor)}; color: ${(props) => transparentize(0.1, props.theme.schema.typeNameColor)};
`; `;
export const TypeName = styled(FieldLabel)` export const TypeName = styled(FieldLabel)`
color: ${props => props.theme.schema.typeNameColor}; color: ${(props) => props.theme.schema.typeNameColor};
`; `;
export const TypeTitle = styled(FieldLabel)` export const TypeTitle = styled(FieldLabel)`
color: ${props => props.theme.schema.typeTitleColor}; color: ${(props) => props.theme.schema.typeTitleColor};
word-break: break-word; word-break: break-word;
`; `;
export const TypeFormat = TypeName; export const TypeFormat = TypeName;
export const RequiredLabel = styled(FieldLabel.withComponent('div'))` export const RequiredLabel = styled(FieldLabel.withComponent('div'))`
color: ${props => props.theme.schema.requireLabelColor}; color: ${(props) => props.theme.schema.requireLabelColor};
font-size: ${props => props.theme.schema.labelsTextSize}; font-size: ${(props) => props.theme.schema.labelsTextSize};
font-weight: normal; font-weight: normal;
margin-left: 20px; margin-left: 20px;
line-height: 1; line-height: 1;

View File

@ -6,7 +6,7 @@ const headerFontSize = {
3: '1.27em', 3: '1.27em',
}; };
export const headerCommonMixin = level => css` export const headerCommonMixin = (level) => css`
font-family: ${({ theme }) => theme.typography.headings.fontFamily}; font-family: ${({ theme }) => theme.typography.headings.fontFamily};
font-weight: ${({ theme }) => theme.typography.headings.fontWeight}; font-weight: ${({ theme }) => theme.typography.headings.fontWeight};
font-size: ${headerFontSize[level]}; font-size: ${headerFontSize[level]};

View File

@ -2,8 +2,8 @@ import { SECTION_ATTR } from '../services/MenuStore';
import styled, { media } from '../styled-components'; import styled, { media } from '../styled-components';
export const MiddlePanel = styled.div<{ compact?: boolean }>` export const MiddlePanel = styled.div<{ compact?: boolean }>`
width: calc(100% - ${props => props.theme.rightPanel.width}); width: calc(100% - ${(props) => props.theme.rightPanel.width});
padding: 0 ${props => props.theme.spacing.sectionHorizontal}px; padding: 0 ${(props) => props.theme.spacing.sectionHorizontal}px;
${({ compact, theme }) => ${({ compact, theme }) =>
media.lessThan('medium', true)` media.lessThan('medium', true)`
@ -14,10 +14,10 @@ export const MiddlePanel = styled.div<{ compact?: boolean }>`
`}; `};
`; `;
export const Section = styled.div.attrs(props => ({ export const Section = styled.div.attrs((props) => ({
[SECTION_ATTR]: props.id, [SECTION_ATTR]: props.id,
}))<{ underlined?: boolean }>` }))<{ underlined?: boolean }>`
padding: ${props => props.theme.spacing.sectionVertical}px 0; padding: ${(props) => props.theme.spacing.sectionVertical}px 0;
&:last-child { &:last-child {
min-height: calc(100vh + 1px); min-height: calc(100vh + 1px);
@ -48,20 +48,20 @@ export const Section = styled.div.attrs(props => ({
`; `;
export const RightPanel = styled.div` export const RightPanel = styled.div`
width: ${props => props.theme.rightPanel.width}; width: ${(props) => props.theme.rightPanel.width};
color: ${({ theme }) => theme.rightPanel.textColor}; color: ${({ theme }) => theme.rightPanel.textColor};
background-color: ${props => props.theme.rightPanel.backgroundColor}; background-color: ${(props) => props.theme.rightPanel.backgroundColor};
padding: 0 ${props => props.theme.spacing.sectionHorizontal}px; padding: 0 ${(props) => props.theme.spacing.sectionHorizontal}px;
${media.lessThan('medium', true)` ${media.lessThan('medium', true)`
width: 100%; width: 100%;
padding: ${props => padding: ${(props) =>
`${props.theme.spacing.sectionVertical}px ${props.theme.spacing.sectionHorizontal}px`}; `${props.theme.spacing.sectionVertical}px ${props.theme.spacing.sectionHorizontal}px`};
`}; `};
`; `;
export const DarkRightPanel = styled(RightPanel)` export const DarkRightPanel = styled(RightPanel)`
background-color: ${props => props.theme.rightPanel.backgroundColor}; background-color: ${(props) => props.theme.rightPanel.backgroundColor};
`; `;
export const Row = styled.div` export const Row = styled.div`

View File

@ -12,7 +12,7 @@ import styled, { createGlobalStyle } from '../styled-components';
* That's why the following ugly fix is required * That's why the following ugly fix is required
*/ */
const PerfectScrollbarConstructor = const PerfectScrollbarConstructor =
PerfectScrollbarNamespace.default || ((PerfectScrollbarNamespace as any) as PerfectScrollbarType); PerfectScrollbarNamespace.default || (PerfectScrollbarNamespace as any as PerfectScrollbarType);
const PSStyling = createGlobalStyle`${psStyles && psStyles.toString()}`; const PSStyling = createGlobalStyle`${psStyles && psStyles.toString()}`;
@ -46,7 +46,7 @@ export class PerfectScrollbar extends React.Component<PerfectScrollbarProps> {
this.inst.destroy(); this.inst.destroy();
} }
handleRef = ref => { handleRef = (ref) => {
this._container = ref; this._container = ref;
}; };
@ -73,7 +73,7 @@ export function PerfectScrollbarWrap(
) { ) {
return ( return (
<OptionsContext.Consumer> <OptionsContext.Consumer>
{options => {(options) =>
!options.nativeScrollbars ? ( !options.nativeScrollbars ? (
<PerfectScrollbar {...props}>{props.children}</PerfectScrollbar> <PerfectScrollbar {...props}>{props.children}</PerfectScrollbar>
) : ( ) : (

View File

@ -33,8 +33,8 @@ export const SampleControlsWrap = styled.div`
`; `;
export const StyledPre = styled(PrismDiv.withComponent('pre'))` export const StyledPre = styled(PrismDiv.withComponent('pre'))`
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${(props) => props.theme.typography.code.fontFamily};
font-size: ${props => props.theme.typography.code.fontSize}; font-size: ${(props) => props.theme.typography.code.fontSize};
overflow-x: auto; overflow-x: auto;
margin: 0; margin: 0;

View File

@ -9,8 +9,8 @@ export const OneOfList = styled.div`
export const OneOfLabel = styled.span` export const OneOfLabel = styled.span`
font-size: 0.9em; font-size: 0.9em;
margin-right: 10px; margin-right: 10px;
color: ${props => props.theme.colors.primary.main}; color: ${(props) => props.theme.colors.primary.main};
font-family: ${props => props.theme.typography.headings.fontFamily}; font-family: ${(props) => props.theme.typography.headings.fontFamily};
} }
`; `;
@ -20,15 +20,15 @@ export const OneOfButton = styled.button<{ active: boolean }>`
margin-bottom: 5px; margin-bottom: 5px;
font-size: 0.8em; font-size: 0.8em;
cursor: pointer; cursor: pointer;
border: 1px solid ${props => props.theme.colors.primary.main}; border: 1px solid ${(props) => props.theme.colors.primary.main};
padding: 2px 10px; padding: 2px 10px;
line-height: 1.5em; line-height: 1.5em;
outline: none; outline: none;
&:focus { &:focus {
box-shadow: 0 0 0 1px ${props => props.theme.colors.primary.main}; box-shadow: 0 0 0 1px ${(props) => props.theme.colors.primary.main};
} }
${props => { ${(props) => {
if (props.active) { if (props.active) {
return ` return `
color: white; color: white;
@ -49,7 +49,7 @@ export const OneOfButton = styled.button<{ active: boolean }>`
export const ArrayOpenningLabel = styled.div` export const ArrayOpenningLabel = styled.div`
font-size: 0.9em; font-size: 0.9em;
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${(props) => props.theme.typography.code.fontFamily};
&::after { &::after {
content: ' ['; content: ' [';
} }
@ -57,7 +57,7 @@ export const ArrayOpenningLabel = styled.div`
export const ArrayClosingLabel = styled.div` export const ArrayClosingLabel = styled.div`
font-size: 0.9em; font-size: 0.9em;
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${(props) => props.theme.typography.code.fontFamily};
&::after { &::after {
content: ']'; content: ']';
} }

View File

@ -35,12 +35,12 @@ class IntShelfIcon extends React.PureComponent<{
} }
export const ShelfIcon = styled(IntShelfIcon)` export const ShelfIcon = styled(IntShelfIcon)`
height: ${props => props.size || '18px'}; height: ${(props) => props.size || '18px'};
width: ${props => props.size || '18px'}; width: ${(props) => props.size || '18px'};
vertical-align: middle; vertical-align: middle;
float: ${props => props.float || ''}; float: ${(props) => props.float || ''};
transition: transform 0.2s ease-out; transition: transform 0.2s ease-out;
transform: rotateZ(${props => directionMap[props.direction || 'down']}); transform: rotateZ(${(props) => directionMap[props.direction || 'down']});
polygon { polygon {
fill: ${({ color, theme }) => fill: ${({ color, theme }) =>
@ -52,9 +52,9 @@ export const Badge = styled.span<{ type: string }>`
display: inline-block; display: inline-block;
padding: 2px 8px; padding: 2px 8px;
margin: 0; margin: 0;
background-color: ${props => props.theme.colors[props.type].main}; background-color: ${(props) => props.theme.colors[props.type].main};
color: ${props => props.theme.colors[props.type].contrastText}; color: ${(props) => props.theme.colors[props.type].contrastText};
font-size: ${props => props.theme.typography.code.fontSize}; font-size: ${(props) => props.theme.typography.code.fontSize};
vertical-align: middle; vertical-align: middle;
line-height: 1.6; line-height: 1.6;
border-radius: 4px; border-radius: 4px;

View File

@ -31,7 +31,7 @@ export const Tabs = styled(ReactTabs)`
font-weight: bold; font-weight: bold;
&.react-tabs__tab--selected { &.react-tabs__tab--selected {
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
background: ${({ theme }) => theme.rightPanel.textColor}; background: ${({ theme }) => theme.rightPanel.textColor};
&:focus { &:focus {
outline: auto; outline: auto;
@ -44,19 +44,19 @@ export const Tabs = styled(ReactTabs)`
} }
&.tab-success { &.tab-success {
color: ${props => props.theme.colors.responses.success.tabTextColor}; color: ${(props) => props.theme.colors.responses.success.tabTextColor};
} }
&.tab-redirect { &.tab-redirect {
color: ${props => props.theme.colors.responses.redirect.tabTextColor}; color: ${(props) => props.theme.colors.responses.redirect.tabTextColor};
} }
&.tab-info { &.tab-info {
color: ${props => props.theme.colors.responses.info.tabTextColor}; color: ${(props) => props.theme.colors.responses.info.tabTextColor};
} }
&.tab-error { &.tab-error {
color: ${props => props.theme.colors.responses.error.tabTextColor}; color: ${(props) => props.theme.colors.responses.error.tabTextColor};
} }
} }
} }
@ -64,7 +64,7 @@ export const Tabs = styled(ReactTabs)`
background: ${({ theme }) => theme.codeBlock.backgroundColor}; background: ${({ theme }) => theme.codeBlock.backgroundColor};
& > div, & > div,
& > pre { & > pre {
padding: ${props => props.theme.spacing.unit * 4}px; padding: ${(props) => props.theme.spacing.unit * 4}px;
margin: 0; margin: 0;
} }
@ -101,7 +101,7 @@ export const SmallTabs = styled(Tabs)`
> .react-tabs__tab-panel { > .react-tabs__tab-panel {
& > div, & > div,
& > pre { & > pre {
padding: ${props => props.theme.spacing.unit * 2}px 0; padding: ${(props) => props.theme.spacing.unit * 2}px 0;
} }
} }
`; `;

View File

@ -22,7 +22,7 @@ export interface ApiInfoProps {
@observer @observer
export class ApiInfo extends React.Component<ApiInfoProps> { export class ApiInfo extends React.Component<ApiInfoProps> {
handleDownloadClick = e => { handleDownloadClick = (e) => {
if (!e.target.href) { if (!e.target.href) {
e.target.href = this.props.store.spec.info.downloadLink; e.target.href = this.props.store.spec.info.downloadLink;
} }
@ -39,7 +39,12 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
const license = const license =
(info.license && ( (info.license && (
<InfoSpan> <InfoSpan>
License: {info.license.identifier ? info.license.identifier : (<a href={info.license.url}>{info.license.name}</a>)} License:{' '}
{info.license.identifier ? (
info.license.identifier
) : (
<a href={info.license.url}>{info.license.name}</a>
)}
</InfoSpan> </InfoSpan>
)) || )) ||
null; null;

View File

@ -13,8 +13,8 @@ export const ApiHeader = styled(H1)`
`; `;
export const DownloadButton = styled.a` export const DownloadButton = styled.a`
border: 1px solid ${props => props.theme.colors.primary.main}; border: 1px solid ${(props) => props.theme.colors.primary.main};
color: ${props => props.theme.colors.primary.main}; color: ${(props) => props.theme.colors.primary.main};
font-weight: normal; font-weight: normal;
margin-left: 0.5em; margin-left: 0.5em;
padding: 4px 8px 4px; padding: 4px 8px 4px;

View File

@ -2,9 +2,9 @@ import * as React from 'react';
import styled from '../../styled-components'; import styled from '../../styled-components';
export const LogoImgEl = styled.img` export const LogoImgEl = styled.img`
max-height: ${props => props.theme.logo.maxHeight}; max-height: ${(props) => props.theme.logo.maxHeight};
max-width: ${props => props.theme.logo.maxWidth}; max-width: ${(props) => props.theme.logo.maxWidth};
padding: ${props => props.theme.logo.gutter}; padding: ${(props) => props.theme.logo.gutter};
width: 100%; width: 100%;
display: block; display: block;
`; `;
@ -18,4 +18,4 @@ const Link = styled.a`
`; `;
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
export const LinkWrap = url => Component => <Link href={url}>{Component}</Link>; export const LinkWrap = (url) => (Component) => <Link href={url}>{Component}</Link>;

View File

@ -14,7 +14,7 @@ export interface PayloadSampleProps {
export class CallbackPayloadSample extends React.Component<PayloadSampleProps> { export class CallbackPayloadSample extends React.Component<PayloadSampleProps> {
render() { render() {
const payloadSample = this.props.callback.codeSamples.find(sample => const payloadSample = this.props.callback.codeSamples.find((sample) =>
isPayloadSample(sample), isPayloadSample(sample),
) as XPayloadSample | undefined; ) as XPayloadSample | undefined;

View File

@ -20,8 +20,15 @@ export class CallbackSamples extends React.Component<CallbackSamplesProps> {
static contextType = OptionsContext; static contextType = OptionsContext;
context: RedocNormalizedOptions; context: RedocNormalizedOptions;
private renderDropdown = props => { private renderDropdown = (props) => {
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />; return (
<DropdownOrLabel
Label={MimeLabel}
Dropdown={InvertedSimpleDropdown}
aria-label="Callback sample dropdown"
{...props}
/>
);
}; };
render() { render() {
@ -32,10 +39,10 @@ export class CallbackSamples extends React.Component<CallbackSamplesProps> {
} }
const operations = callbacks const operations = callbacks
.map(callback => callback.operations.map(operation => operation)) .map((callback) => callback.operations.map((operation) => operation))
.reduce((a, b) => a.concat(b), []); .reduce((a, b) => a.concat(b), []);
const hasSamples = operations.some(operation => operation.codeSamples.length > 0); const hasSamples = operations.some((operation) => operation.codeSamples.length > 0);
if (!hasSamples) { if (!hasSamples) {
return null; return null;
@ -43,8 +50,8 @@ export class CallbackSamples extends React.Component<CallbackSamplesProps> {
const dropdownOptions = operations.map((callback, idx) => { const dropdownOptions = operations.map((callback, idx) => {
return { return {
value: `${callback.httpVerb.toUpperCase()}: ${callback.name}`, label: `${callback.httpVerb.toUpperCase()}: ${callback.name}`,
idx, value: idx.toString(),
}; };
}); });
@ -59,7 +66,7 @@ export class CallbackSamples extends React.Component<CallbackSamplesProps> {
label={'Callback'} label={'Callback'}
options={dropdownOptions} options={dropdownOptions}
> >
{callback => ( {(callback) => (
<CallbackPayloadSample <CallbackPayloadSample
key="callbackPayloadSample" key="callbackPayloadSample"
callback={callback} callback={callback}
@ -75,5 +82,5 @@ export class CallbackSamples extends React.Component<CallbackSamplesProps> {
export const SamplesWrapper = styled.div` export const SamplesWrapper = styled.div`
background: ${({ theme }) => theme.codeBlock.backgroundColor}; background: ${({ theme }) => theme.codeBlock.backgroundColor};
padding: ${props => props.theme.spacing.unit * 4}px; padding: ${(props) => props.theme.spacing.unit * 4}px;
`; `;

View File

@ -19,7 +19,7 @@ export class CallbacksList extends React.PureComponent<CallbacksListProps> {
return ( return (
<div> <div>
<CallbacksHeader> Callbacks </CallbacksHeader> <CallbacksHeader> Callbacks </CallbacksHeader>
{callbacks.map(callback => { {callbacks.map((callback) => {
return callback.operations.map((operation, index) => { return callback.operations.map((operation, index) => {
return ( return (
<CallbackOperation key={`${callback.name}_${index}`} callbackOperation={operation} /> <CallbackOperation key={`${callback.name}_${index}`} callbackOperation={operation} />

View File

@ -17,7 +17,7 @@ export class ContentItems extends React.Component<{
if (items.length === 0) { if (items.length === 0) {
return null; return null;
} }
return items.map(item => { return items.map((item) => {
return <ContentItem key={item.id} item={item} />; return <ContentItem key={item.id} item={item} />;
}); });
} }
@ -61,7 +61,7 @@ export class ContentItem extends React.Component<ContentItemProps> {
} }
} }
const middlePanelWrap = component => <MiddlePanel compact={true}>{component}</MiddlePanel>; const middlePanelWrap = (component) => <MiddlePanel compact={true}>{component}</MiddlePanel>;
@observer @observer
export class SectionItem extends React.Component<ContentItemProps> { export class SectionItem extends React.Component<ContentItemProps> {

View File

@ -10,7 +10,14 @@ export interface DropdownOrLabelProps extends DropdownProps {
export function DropdownOrLabel(props: DropdownOrLabelProps): JSX.Element { export function DropdownOrLabel(props: DropdownOrLabelProps): JSX.Element {
const { Label = MimeLabel, Dropdown = SimpleDropdown } = props; const { Label = MimeLabel, Dropdown = SimpleDropdown } = props;
if (props.options.length === 1) { if (props.options.length === 1) {
return <Label>{props.options[0].value}</Label>; return <Label>{props.options[0].label}</Label>;
} }
return <Dropdown {...props} searchable={false} />; return (
<Dropdown
className={'react-dropdown-container'}
classNamePrefix={'react-dropdown'}
isSearchable={false}
{...props}
/>
);
} }

View File

@ -47,7 +47,7 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
// TODO: highlight server variables, e.g. https://{user}.test.com // TODO: highlight server variables, e.g. https://{user}.test.com
return ( return (
<OptionsContext.Consumer> <OptionsContext.Consumer>
{options => ( {(options) => (
<OperationEndpointWrap> <OperationEndpointWrap>
<EndpointInfo onClick={this.toggle} expanded={expanded} inverted={inverted}> <EndpointInfo onClick={this.toggle} expanded={expanded} inverted={inverted}>
<HttpVerb type={operation.httpVerb} compact={this.props.compact}> <HttpVerb type={operation.httpVerb} compact={this.props.compact}>
@ -63,7 +63,7 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
/> />
</EndpointInfo> </EndpointInfo>
<ServersOverlay expanded={expanded} aria-hidden={!expanded}> <ServersOverlay expanded={expanded} aria-hidden={!expanded}>
{operation.servers.map(server => { {operation.servers.map((server) => {
const normalizedUrl = options.expandDefaultServerVariables const normalizedUrl = options.expandDefaultServerVariables
? expandDefaultServerVariables(server.url, server.variables) ? expandDefaultServerVariables(server.url, server.variables)
: server.url; : server.url;

View File

@ -7,7 +7,7 @@ export const OperationEndpointWrap = styled.div`
`; `;
export const ServerRelativeURL = styled.span` export const ServerRelativeURL = styled.span`
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${(props) => props.theme.typography.code.fontFamily};
margin-left: 10px; margin-left: 10px;
flex: 1; flex: 1;
overflow-x: hidden; overflow-x: hidden;
@ -20,22 +20,22 @@ export const EndpointInfo = styled.button<{ expanded?: boolean; inverted?: boole
width: 100%; width: 100%;
text-align: left; text-align: left;
cursor: pointer; cursor: pointer;
padding: 10px 30px 10px ${props => (props.inverted ? '10px' : '20px')}; padding: 10px 30px 10px ${(props) => (props.inverted ? '10px' : '20px')};
border-radius: ${props => (props.inverted ? '0' : '4px 4px 0 0')}; border-radius: ${(props) => (props.inverted ? '0' : '4px 4px 0 0')};
background-color: ${props => background-color: ${(props) =>
props.inverted ? 'transparent' : props.theme.codeBlock.backgroundColor}; props.inverted ? 'transparent' : props.theme.codeBlock.backgroundColor};
display: flex; display: flex;
white-space: nowrap; white-space: nowrap;
align-items: center; align-items: center;
border: ${props => (props.inverted ? '0' : '1px solid transparent')}; border: ${(props) => (props.inverted ? '0' : '1px solid transparent')};
border-bottom: ${props => (props.inverted ? '1px solid #ccc' : '0')}; border-bottom: ${(props) => (props.inverted ? '1px solid #ccc' : '0')};
transition: border-color 0.25s ease; transition: border-color 0.25s ease;
${props => ${(props) =>
(props.expanded && !props.inverted && `border-color: ${props.theme.colors.border.dark};`) || ''} (props.expanded && !props.inverted && `border-color: ${props.theme.colors.border.dark};`) || ''}
.${ServerRelativeURL} { .${ServerRelativeURL} {
color: ${props => (props.inverted ? props.theme.colors.text.primary : '#ffffff')} color: ${(props) => (props.inverted ? props.theme.colors.text.primary : '#ffffff')};
} }
&:focus { &:focus {
box-shadow: inset 0 2px 2px rgba(0, 0, 0, 0.45), 0 2px 0 rgba(128, 128, 128, 0.25); box-shadow: inset 0 2px 2px rgba(0, 0, 0, 0.45), 0 2px 0 rgba(128, 128, 128, 0.25);
@ -45,13 +45,13 @@ export const EndpointInfo = styled.button<{ expanded?: boolean; inverted?: boole
export const HttpVerb = styled.span.attrs((props: { type: string; compact?: boolean }) => ({ export const HttpVerb = styled.span.attrs((props: { type: string; compact?: boolean }) => ({
className: `http-verb ${props.type}`, className: `http-verb ${props.type}`,
}))<{ type: string; compact?: boolean }>` }))<{ type: string; compact?: boolean }>`
font-size: ${props => (props.compact ? '0.8em' : '0.929em')}; font-size: ${(props) => (props.compact ? '0.8em' : '0.929em')};
line-height: ${props => (props.compact ? '18px' : '20px')}; line-height: ${(props) => (props.compact ? '18px' : '20px')};
background-color: ${props => props.theme.colors.http[props.type] || '#999999'}; background-color: ${(props) => props.theme.colors.http[props.type] || '#999999'};
color: #ffffff; color: #ffffff;
padding: ${props => (props.compact ? '2px 8px' : '3px 10px')}; padding: ${(props) => (props.compact ? '2px 8px' : '3px 10px')};
text-transform: uppercase; text-transform: uppercase;
font-family: ${props => props.theme.typography.headings.fontFamily}; font-family: ${(props) => props.theme.typography.headings.fontFamily};
margin: 0; margin: 0;
`; `;
@ -68,7 +68,7 @@ export const ServersOverlay = styled.div<{ expanded: boolean }>`
border-bottom-right-radius: 4px; border-bottom-right-radius: 4px;
transition: all 0.25s ease; transition: all 0.25s ease;
visibility: hidden; visibility: hidden;
${props => (props.expanded ? 'visibility: visible;' : 'transform: translateY(-50%) scaleY(0);')} ${(props) => (props.expanded ? 'visibility: visible;' : 'transform: translateY(-50%) scaleY(0);')}
`; `;
export const ServerItem = styled.div` export const ServerItem = styled.div`
@ -80,8 +80,8 @@ export const ServerUrl = styled.div`
border: 1px solid #ccc; border: 1px solid #ccc;
background: #fff; background: #fff;
word-break: break-all; word-break: break-all;
color: ${props => props.theme.colors.primary.main}; color: ${(props) => props.theme.colors.primary.main};
> span { > span {
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
} }
`; `;

View File

@ -81,7 +81,7 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
} }
const ToggleButton = styled.span` const ToggleButton = styled.span`
color: ${props => props.theme.colors.primary.main}; color: ${(props) => props.theme.colors.primary.main};
vertical-align: middle; vertical-align: middle;
font-size: 13px; font-size: 13px;
line-height: 20px; line-height: 20px;

View File

@ -23,10 +23,10 @@ export class Extensions extends React.PureComponent<ExtensionsProps> {
const exts = this.props.extensions; const exts = this.props.extensions;
return ( return (
<OptionsContext.Consumer> <OptionsContext.Consumer>
{options => ( {(options) => (
<> <>
{options.showExtensions && {options.showExtensions &&
Object.keys(exts).map(key => ( Object.keys(exts).map((key) => (
<Extension key={key}> <Extension key={key}>
<FieldLabel> {key.substring(2)}: </FieldLabel>{' '} <FieldLabel> {key.substring(2)}: </FieldLabel>{' '}
<ExtensionValue> <ExtensionValue>

View File

@ -38,7 +38,7 @@ export class Field extends React.Component<FieldProps> {
} }
}; };
handleKeyPress = e => { handleKeyPress = (e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
this.toggle(); this.toggle();

View File

@ -13,7 +13,7 @@ export class ConstraintsView extends React.PureComponent<ConstraintsViewProps> {
return ( return (
<span> <span>
{' '} {' '}
{this.props.constraints.map(constraint => ( {this.props.constraints.map((constraint) => (
<ConstraintItem key={constraint}> {constraint} </ConstraintItem> <ConstraintItem key={constraint}> {constraint} </ConstraintItem>
))} ))}
</span> </span>

View File

@ -59,7 +59,9 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
} else { } else {
const label = l('example') + ':'; const label = l('example') + ':';
const raw = !!field.in; const raw = !!field.in;
renderedExamples = <FieldDetail label={label} value={getSerializedValue(field, field.example)} raw={raw} />; renderedExamples = (
<FieldDetail label={label} value={getSerializedValue(field, field.example)} raw={raw} />
);
} }
} }
@ -78,14 +80,16 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
)} )}
{schema.contentEncoding && ( {schema.contentEncoding && (
<TypeFormat> <TypeFormat>
{' '}&lt; {' '}
&lt;
{schema.contentEncoding} {schema.contentEncoding}
&gt;{' '} &gt;{' '}
</TypeFormat> </TypeFormat>
)} )}
{schema.contentMediaType && ( {schema.contentMediaType && (
<TypeFormat> <TypeFormat>
{' '}&lt; {' '}
&lt;
{schema.contentMediaType} {schema.contentMediaType}
&gt;{' '} &gt;{' '}
</TypeFormat> </TypeFormat>
@ -124,7 +128,7 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
<ExternalDocumentation externalDocs={schema.externalDocs} compact={true} /> <ExternalDocumentation externalDocs={schema.externalDocs} compact={true} />
)} )}
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null} {(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null}
{field.const && (<FieldDetail label={l('const') + ':'} value={field.const} />) || null} {(field.const && <FieldDetail label={l('const') + ':'} value={field.const} />) || null}
</div> </div>
); );
} }
@ -142,7 +146,8 @@ function Examples({ field }: { field: FieldModel }) {
{Object.values(field.examples).map((example, idx) => { {Object.values(field.examples).map((example, idx) => {
return ( return (
<li key={idx}> <li key={idx}>
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> - {example.summary || example.description} <ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> -{' '}
{example.summary || example.description}
</li> </li>
); );
})} })}
@ -160,7 +165,6 @@ function getSerializedValue(field: FieldModel, example: any) {
} }
} }
const ExamplesList = styled.ul` const ExamplesList = styled.ul`
margin-top: 1em; margin-top: 1em;
padding-left: 0; padding-left: 0;

View File

@ -32,10 +32,10 @@ export class GenericChildrenSwitcher<T> extends React.Component<
}; };
} }
switchItem = ({ idx }) => { switchItem = ({ value }) => {
if (this.props.items) { if (this.props.items) {
this.setState({ this.setState({
activeItemIdx: idx, activeItemIdx: parseInt(value, 10),
}); });
} }
}; };
@ -61,7 +61,7 @@ export class GenericChildrenSwitcher<T> extends React.Component<
<> <>
<Wrapper> <Wrapper>
{this.props.renderDropdown({ {this.props.renderDropdown({
value: this.props.options[this.state.activeItemIdx].value, value: this.props.options[this.state.activeItemIdx],
options: this.props.options, options: this.props.options,
onChange: this.switchItem, onChange: this.switchItem,
ariaLabel: this.props.label || 'Callback', ariaLabel: this.props.label || 'Callback',

View File

@ -9,7 +9,7 @@ const LoadingMessage = styled.div<{ color: string }>`
text-align: center; text-align: center;
font-size: 25px; font-size: 25px;
margin: 30px 0 20px 0; margin: 30px 0 20px 0;
color: ${props => props.color}; color: ${(props) => props.color};
`; `;
export interface LoadingProps { export interface LoadingProps {

View File

@ -31,6 +31,6 @@ export const Spinner = styled(_Spinner)`
margin-left: -25px; margin-left: -25px;
path { path {
fill: ${props => props.color}; fill: ${(props) => props.color};
} }
`; `;

View File

@ -15,15 +15,15 @@ export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
render() { render() {
return ( return (
<OptionsConsumer> <OptionsConsumer>
{options => ( {(options) => (
<StoreConsumer>{store => this.renderWithOptionsAndStore(options, store)}</StoreConsumer> <StoreConsumer>{(store) => this.renderWithOptionsAndStore(options, store)}</StoreConsumer>
)} )}
</OptionsConsumer> </OptionsConsumer>
); );
} }
renderWithOptionsAndStore(options: RedocNormalizedOptions, store?: AppStore) { renderWithOptionsAndStore(options: RedocNormalizedOptions, store?: AppStore) {
const { source, htmlWrap = i => i } = this.props; const { source, htmlWrap = (i) => i } = this.props;
if (!store) { if (!store) {
throw new Error('When using components in markdown, store prop must be provided'); throw new Error('When using components in markdown, store prop must be provided');
} }

View File

@ -16,7 +16,7 @@ export function SanitizedMarkdownHTML(
return ( return (
<OptionsConsumer> <OptionsConsumer>
{options => ( {(options) => (
<Wrap <Wrap
className={'redoc-markdown ' + (props.className || '')} className={'redoc-markdown ' + (props.className || '')}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{

View File

@ -7,14 +7,14 @@ import { StyledComponent } from 'styled-components';
export const linksCss = css` export const linksCss = css`
a { a {
text-decoration: none; text-decoration: none;
color: ${props => props.theme.typography.links.color}; color: ${(props) => props.theme.typography.links.color};
&:visited { &:visited {
color: ${props => props.theme.typography.links.visited}; color: ${(props) => props.theme.typography.links.visited};
} }
&:hover { &:hover {
color: ${props => props.theme.typography.links.hover}; color: ${(props) => props.theme.typography.links.hover};
} }
} }
`; `;
@ -26,10 +26,9 @@ export const StyledMarkdownBlock = styled(
{ compact?: boolean; inline?: boolean } { compact?: boolean; inline?: boolean }
>, >,
)` )`
font-family: ${(props) => props.theme.typography.fontFamily};
font-family: ${props => props.theme.typography.fontFamily}; font-weight: ${(props) => props.theme.typography.fontWeightRegular};
font-weight: ${props => props.theme.typography.fontWeightRegular}; line-height: ${(props) => props.theme.typography.lineHeight};
line-height: ${props => props.theme.typography.lineHeight};
p { p {
&:last-child { &:last-child {
@ -56,35 +55,35 @@ export const StyledMarkdownBlock = styled(
h1 { h1 {
${headerCommonMixin(1)}; ${headerCommonMixin(1)};
color: ${props => props.theme.colors.primary.main}; color: ${(props) => props.theme.colors.primary.main};
margin-top: 0; margin-top: 0;
} }
h2 { h2 {
${headerCommonMixin(2)}; ${headerCommonMixin(2)};
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
} }
code { code {
color: ${({ theme }) => theme.typography.code.color}; color: ${({ theme }) => theme.typography.code.color};
background-color: ${({ theme }) => theme.typography.code.backgroundColor}; background-color: ${({ theme }) => theme.typography.code.backgroundColor};
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${(props) => props.theme.typography.code.fontFamily};
border-radius: 2px; border-radius: 2px;
border: 1px solid rgba(38, 50, 56, 0.1); border: 1px solid rgba(38, 50, 56, 0.1);
padding: 0 ${({ theme }) => theme.spacing.unit}px; padding: 0 ${({ theme }) => theme.spacing.unit}px;
font-size: ${props => props.theme.typography.code.fontSize}; font-size: ${(props) => props.theme.typography.code.fontSize};
font-weight: ${({ theme }) => theme.typography.code.fontWeight}; font-weight: ${({ theme }) => theme.typography.code.fontWeight};
word-break: break-word; word-break: break-word;
} }
pre { pre {
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${(props) => props.theme.typography.code.fontFamily};
white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')}; white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
background-color: ${({ theme }) => theme.codeBlock.backgroundColor}; background-color: ${({ theme }) => theme.codeBlock.backgroundColor};
color: white; color: white;
padding: ${props => props.theme.spacing.unit * 4}px; padding: ${(props) => props.theme.spacing.unit * 4}px;
overflow-x: auto; overflow-x: auto;
line-height: normal; line-height: normal;
border-radius: 0px; border-radius: 0px;
@ -121,7 +120,8 @@ export const StyledMarkdownBlock = styled(
margin: 0; margin: 0;
margin-bottom: 1em; margin-bottom: 1em;
ul, ol { ul,
ol {
margin-bottom: 0; margin-bottom: 0;
margin-top: 0; margin-top: 0;
} }

View File

@ -20,9 +20,9 @@ export interface MediaTypesSwitchProps {
@observer @observer
export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> { export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
switchMedia = ({ idx }) => { switchMedia = ({ value }) => {
if (this.props.content) { if (this.props.content) {
this.props.content.activate(idx); this.props.content.activate(parseInt(value, 10));
} }
}; };
@ -35,8 +35,8 @@ export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
const options = content.mediaTypes.map((mime, idx) => { const options = content.mediaTypes.map((mime, idx) => {
return { return {
value: mime.name, label: mime.name,
idx, value: idx.toString(),
}; };
}); });
@ -54,7 +54,7 @@ export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
<> <>
<Wrapper> <Wrapper>
{this.props.renderDropdown({ {this.props.renderDropdown({
value: options[activeMimeIdx].value, value: options[activeMimeIdx],
options, options,
onChange: this.switchMedia, onChange: this.switchMedia,
ariaLabel: 'Content type', ariaLabel: 'Content type',

View File

@ -28,7 +28,7 @@ const PARAM_PLACES = ['path', 'query', 'cookie', 'header'];
export class Parameters extends React.PureComponent<ParametersProps> { export class Parameters extends React.PureComponent<ParametersProps> {
orderParams(params: FieldModel[]): Record<string, FieldModel[]> { orderParams(params: FieldModel[]): Record<string, FieldModel[]> {
const res = {}; const res = {};
params.forEach(param => { params.forEach((param) => {
safePush(res, param.in, param); safePush(res, param.in, param);
}); });
return res; return res;
@ -50,7 +50,7 @@ export class Parameters extends React.PureComponent<ParametersProps> {
return ( return (
<> <>
{paramsPlaces.map(place => ( {paramsPlaces.map((place) => (
<ParametersGroup key={place} place={place} parameters={paramsMap[place]} /> <ParametersGroup key={place} place={place} parameters={paramsMap[place]} />
))} ))}
{bodyContent && <BodyContent content={bodyContent} description={bodyDescription} />} {bodyContent && <BodyContent content={bodyContent} description={bodyDescription} />}
@ -62,12 +62,15 @@ export class Parameters extends React.PureComponent<ParametersProps> {
function DropdownWithinHeader(props) { function DropdownWithinHeader(props) {
return ( return (
<UnderlinedHeader key="header"> <UnderlinedHeader key="header">
Request Body schema: <DropdownOrLabel {...props} /> Request Body schema: <DropdownOrLabel {...props} aria-label="Request body schema dropdown" />
</UnderlinedHeader> </UnderlinedHeader>
); );
} }
export function BodyContent(props: { content: MediaContentModel; description?: string }): JSX.Element { export function BodyContent(props: {
content: MediaContentModel;
description?: string;
}): JSX.Element {
const { content, description } = props; const { content, description } = props;
const { isRequestType } = content; const { isRequestType } = content;
return ( return (

View File

@ -21,9 +21,10 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps, Media
state = { state = {
activeIdx: 0, activeIdx: 0,
}; };
switchMedia = ({ idx }) => {
switchMedia = ({ value }) => {
this.setState({ this.setState({
activeIdx: idx, activeIdx: parseInt(value, 10),
}); });
}; };
render() { render() {
@ -41,8 +42,8 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps, Media
if (examplesNames.length > 1) { if (examplesNames.length > 1) {
const options = examplesNames.map((name, idx) => { const options = examplesNames.map((name, idx) => {
return { return {
value: examples[name].summary || name, label: examples[name].summary || name,
idx, value: idx.toString(),
}; };
}); });
@ -54,7 +55,7 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps, Media
<DropdownWrapper> <DropdownWrapper>
<DropdownLabel>Example</DropdownLabel> <DropdownLabel>Example</DropdownLabel>
{this.props.renderDropdown({ {this.props.renderDropdown({
value: options[activeIdx].value, value: options[activeIdx],
options, options,
onChange: this.switchMedia, onChange: this.switchMedia,
ariaLabel: 'Example', ariaLabel: 'Example',

View File

@ -21,7 +21,7 @@ export class PayloadSamples extends React.Component<PayloadSamplesProps> {
return ( return (
<MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown} withLabel={true}> <MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown} withLabel={true}>
{mediaType => ( {(mediaType) => (
<MediaTypeSamples <MediaTypeSamples
key="samples" key="samples"
mediaType={mediaType} mediaType={mediaType}
@ -32,7 +32,14 @@ export class PayloadSamples extends React.Component<PayloadSamplesProps> {
); );
} }
private renderDropdown = props => { private renderDropdown = (props) => {
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />; return (
<DropdownOrLabel
Label={MimeLabel}
Dropdown={InvertedSimpleDropdown}
aria-label="Payload sample dropdown"
{...props}
/>
);
}; };
} }

View File

@ -29,49 +29,48 @@ export const DropdownWrapper = styled.div`
export const InvertedSimpleDropdown = styled(StyledDropdown)` export const InvertedSimpleDropdown = styled(StyledDropdown)`
&& { && {
margin-left: 10px;
text-transform: none;
font-size: 0.929em;
margin: 0 0 10px 0;
display: block; display: block;
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
font-size: 1em;
border: none; border: none;
margin: 0 0 10px 0;
padding: 0.9em 1.6em 0.9em 0.9em; padding: 0.9em 1.6em 0.9em 0.9em;
box-shadow: none; box-shadow: none;
text-transform: none;
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
&:hover, &:hover,
&:focus-within { &:focus-within {
border: none; border-bottom: 1px solid ${({ theme }) => theme.rightPanel.textColor};
border-right: 1px solid ${({ theme }) => theme.rightPanel.textColor};
border-left: 1px solid ${({ theme }) => theme.rightPanel.textColor};
border-top: none;
box-shadow: none; box-shadow: none;
}
&:focus-within {
background-color: ${({ theme }) => transparentize(0.3, theme.rightPanel.backgroundColor)}; background-color: ${({ theme }) => transparentize(0.3, theme.rightPanel.backgroundColor)};
} }
.dropdown-arrow { .react-dropdown__control {
border-top-color: ${({ theme }) => theme.rightPanel.textColor}; border: transparent;
} background-color: transparent;
.dropdown-selector-value {
text-overflow: ellipsis; .react-dropdown__single-value {
white-space: nowrap;
overflow: hidden;
color: ${({ theme }) => theme.rightPanel.textColor}; color: ${({ theme }) => theme.rightPanel.textColor};
} }
.dropdown-selector-content { .react-dropdown__indicator {
margin: 0; border-color: ${({ theme }) => theme.rightPanel.textColor} transparent transparent;
margin-top: 2px;
.dropdown-option {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
} }
} }
.react-dropdown__menu {
left: 0;
}
} }
`; `;
export const NoSampleLabel = styled.div` export const NoSampleLabel = styled.div`
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${(props) => props.theme.typography.code.fontFamily};
font-size: 12px; font-size: 12px;
color: #ee807f; color: #ee807f;
`; `;

View File

@ -29,7 +29,7 @@ export const ApiContentWrap = styled.div`
z-index: 1; z-index: 1;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
width: calc(100% - ${props => props.theme.sidebar.width}); width: calc(100% - ${(props) => props.theme.sidebar.width});
${media.lessThan('small', true)` ${media.lessThan('small', true)`
width: 100%; width: 100%;
`}; `};

View File

@ -1,6 +1,10 @@
import * as React from 'react'; import * as React from 'react';
import { argValueToBoolean, RedocNormalizedOptions, RedocRawOptions } from '../services/RedocNormalizedOptions'; import {
argValueToBoolean,
RedocNormalizedOptions,
RedocRawOptions,
} from '../services/RedocNormalizedOptions';
import { ErrorBoundary } from './ErrorBoundary'; import { ErrorBoundary } from './ErrorBoundary';
import { Loading } from './Loading/Loading'; import { Loading } from './Loading/Loading';
import { Redoc } from './Redoc/Redoc'; import { Redoc } from './Redoc/Redoc';
@ -32,4 +36,4 @@ export const RedocStandalone = function (props: RedocStandaloneProps) {
</StoreBuilder> </StoreBuilder>
</ErrorBoundary> </ErrorBoundary>
); );
} };

View File

@ -31,13 +31,13 @@ export class RequestSamples extends React.Component<RequestSamplesProps> {
<Tabs defaultIndex={0}> <Tabs defaultIndex={0}>
<TabList hidden={hideTabList}> <TabList hidden={hideTabList}>
{samples.map(sample => ( {samples.map((sample) => (
<Tab key={sample.lang + '_' + (sample.label || '')}> <Tab key={sample.lang + '_' + (sample.label || '')}>
{sample.label !== undefined ? sample.label : sample.lang} {sample.label !== undefined ? sample.label : sample.lang}
</Tab> </Tab>
))} ))}
</TabList> </TabList>
{samples.map(sample => ( {samples.map((sample) => (
<TabPanel key={sample.lang + '_' + (sample.label || '')}> <TabPanel key={sample.lang + '_' + (sample.label || '')}>
{isPayloadSample(sample) ? ( {isPayloadSample(sample) ? (
<div> <div>

View File

@ -17,7 +17,7 @@ export class ResponseSamples extends React.Component<ResponseSamplesProps> {
render() { render() {
const { operation } = this.props; const { operation } = this.props;
const responses = operation.responses.filter(response => { const responses = operation.responses.filter((response) => {
return response.content && response.content.hasSample; return response.content && response.content.hasSample;
}); });
@ -28,13 +28,13 @@ export class ResponseSamples extends React.Component<ResponseSamplesProps> {
<Tabs defaultIndex={0}> <Tabs defaultIndex={0}>
<TabList> <TabList>
{responses.map(response => ( {responses.map((response) => (
<Tab className={'tab-' + response.type} key={response.code}> <Tab className={'tab-' + response.type} key={response.code}>
{response.code} {response.code}
</Tab> </Tab>
))} ))}
</TabList> </TabList>
{responses.map(response => ( {responses.map((response) => (
<TabPanel key={response.code}> <TabPanel key={response.code}>
<div> <div>
<PayloadSamples content={response.content!} /> <PayloadSamples content={response.content!} />

View File

@ -14,7 +14,7 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> {
render() { render() {
const { headers, type, summary, description, code, expanded, content } = this.props.response; const { headers, type, summary, description, code, expanded, content } = this.props.response;
const mimes = const mimes =
content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined); content === undefined ? [] : content.mediaTypes.filter((mime) => mime.schema !== undefined);
const empty = headers.length === 0 && mimes.length === 0 && !description; const empty = headers.length === 0 && mimes.length === 0 && !description;

View File

@ -26,10 +26,10 @@ export class ResponseDetails extends React.PureComponent<{ response: ResponseMod
); );
} }
private renderDropdown = props => { private renderDropdown = (props) => {
return ( return (
<UnderlinedHeader key="header"> <UnderlinedHeader key="header">
Response Schema: <DropdownOrLabel {...props} /> Response Schema: <DropdownOrLabel aria-label="Response schema dropdown" {...props} />
</UnderlinedHeader> </UnderlinedHeader>
); );
}; };

View File

@ -28,7 +28,7 @@ export class ResponsesList extends React.PureComponent<ResponseListProps> {
return ( return (
<div> <div>
<ResponsesHeader>{isCallback ? l('callbackResponses') : l('responses')}</ResponsesHeader> <ResponsesHeader>{isCallback ? l('callbackResponses') : l('responses')}</ResponsesHeader>
{responses.map(response => { {responses.map((response) => {
return <ResponseView key={response.code} response={response} />; return <ResponseView key={response.code} response={response} />;
})} })}
</div> </div>

View File

@ -16,14 +16,17 @@ export class ArraySchema extends React.PureComponent<SchemaProps> {
const schema = this.props.schema; const schema = this.props.schema;
const itemsSchema = schema.items; const itemsSchema = schema.items;
const minMaxItems = schema.minItems === undefined && schema.maxItems === undefined ? const minMaxItems =
'' : schema.minItems === undefined && schema.maxItems === undefined
`(${humanizeConstraints(schema)})`; ? ''
: `(${humanizeConstraints(schema)})`;
if (schema.displayType && !itemsSchema && !minMaxItems.length) { if (schema.displayType && !itemsSchema && !minMaxItems.length) {
return (<div> return (
<div>
<TypeName>{schema.displayType}</TypeName> <TypeName>{schema.displayType}</TypeName>
</div>); </div>
);
} }
return ( return (

View File

@ -33,12 +33,12 @@ export class DiscriminatorDropdown extends React.Component<{
const options = parent.oneOf.map((subSchema, idx) => { const options = parent.oneOf.map((subSchema, idx) => {
return { return {
value: subSchema.title, value: idx.toString(),
idx, label: subSchema.title,
}; };
}); });
const activeValue = options[parent.activeOneOf].value; const activeValue = options[parent.activeOneOf];
this.sortOptions(options, enumValues); this.sortOptions(options, enumValues);
@ -47,12 +47,16 @@ export class DiscriminatorDropdown extends React.Component<{
value={activeValue} value={activeValue}
options={options} options={options}
onChange={this.changeActiveChild} onChange={this.changeActiveChild}
ariaLabel="Example" className={'react-dropdown-container'}
classNamePrefix={'react-dropdown'}
isSearchable={false}
aria-label="Example"
/> />
); );
} }
changeActiveChild = (option: DropdownOption) => { changeActiveChild = ({ value }) => {
this.props.parent.activateOneOf(option.idx); const idx = parseInt(value, 10);
this.props.parent.activateOneOf(idx);
}; };
} }

View File

@ -36,7 +36,7 @@ export class ObjectSchema extends React.Component<ObjectSchemaProps> {
const needFilter = this.props.skipReadOnly || this.props.skipWriteOnly; const needFilter = this.props.skipReadOnly || this.props.skipWriteOnly;
const filteredFields = needFilter const filteredFields = needFilter
? fields.filter(item => { ? fields.filter((item) => {
return !( return !(
(this.props.skipReadOnly && item.schema.readOnly) || (this.props.skipReadOnly && item.schema.readOnly) ||
(this.props.skipWriteOnly && item.schema.writeOnly) (this.props.skipWriteOnly && item.schema.writeOnly)

View File

@ -73,7 +73,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
} }
// TODO: maybe adjust FieldDetails to accept schema // TODO: maybe adjust FieldDetails to accept schema
const field = ({ const field = {
schema, schema,
name: '', name: '',
required: false, required: false,
@ -82,7 +82,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
deprecated: false, deprecated: false,
toggle: () => null, toggle: () => null,
expanded: false, expanded: false,
} as any) as FieldModel; // cast needed for hot-loader to not fail } as any as FieldModel; // cast needed for hot-loader to not fail
return ( return (
<div> <div>

View File

@ -74,8 +74,15 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
); );
} }
private renderDropdown = props => { private renderDropdown = (props) => {
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />; return (
<DropdownOrLabel
Label={MimeLabel}
Dropdown={InvertedSimpleDropdown}
aria-label="Schema definition dropdown"
{...props}
/>
);
}; };
} }
@ -83,7 +90,7 @@ const MediaSamplesWrap = styled.div`
background: ${({ theme }) => theme.codeBlock.backgroundColor}; background: ${({ theme }) => theme.codeBlock.backgroundColor};
& > div, & > div,
& > pre { & > pre {
padding: ${props => props.theme.spacing.unit * 4}px; padding: ${(props) => props.theme.spacing.unit * 4}px;
margin: 0; margin: 0;
} }

View File

@ -100,7 +100,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
setResults(results: SearchResult[], term: string) { setResults(results: SearchResult[], term: string) {
this.setState({ this.setState({
results, results,
noResults: results.length === 0 noResults: results.length === 0,
}); });
this.props.marker.mark(term); this.props.marker.mark(term);
} }
@ -108,7 +108,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
@bind @bind
@debounce(400) @debounce(400)
searchCallback(searchTerm: string) { searchCallback(searchTerm: string) {
this.props.search.search(searchTerm).then(res => { this.props.search.search(searchTerm).then((res) => {
this.setResults(res, searchTerm); this.setResults(res, searchTerm);
}); });
} }
@ -130,7 +130,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
render() { render() {
const { activeItemIdx } = this.state; const { activeItemIdx } = this.state;
const results = this.state.results.map(res => ({ const results = this.state.results.map((res) => ({
item: this.props.getItemById(res.meta)!, item: this.props.getItemById(res.meta)!,
score: res.score, score: res.score,
})); }));

View File

@ -11,11 +11,11 @@ export const SearchWrap = styled.div`
export const SearchInput = styled.input.attrs(() => ({ export const SearchInput = styled.input.attrs(() => ({
className: 'search-input', className: 'search-input',
}))` }))`
width: calc(100% - ${props => props.theme.spacing.unit * 8}px); width: calc(100% - ${(props) => props.theme.spacing.unit * 8}px);
box-sizing: border-box; box-sizing: border-box;
margin: 0 ${props => props.theme.spacing.unit * 4}px; margin: 0 ${(props) => props.theme.spacing.unit * 4}px;
padding: 5px ${props => props.theme.spacing.unit * 2}px 5px padding: 5px ${(props) => props.theme.spacing.unit * 2}px 5px
${props => props.theme.spacing.unit * 4}px; ${(props) => props.theme.spacing.unit * 4}px;
border: 0; border: 0;
border-bottom: 1px solid border-bottom: 1px solid
${({ theme }) => ${({ theme }) =>
@ -26,7 +26,7 @@ export const SearchInput = styled.input.attrs(() => ({
font-family: ${({ theme }) => theme.typography.fontFamily}; font-family: ${({ theme }) => theme.typography.fontFamily};
font-weight: bold; font-weight: bold;
font-size: 13px; font-size: 13px;
color: ${props => props.theme.sidebar.textColor}; color: ${(props) => props.theme.sidebar.textColor};
background-color: transparent; background-color: transparent;
outline: none; outline: none;
`; `;
@ -46,19 +46,19 @@ export const SearchIcon = styled((props: { className?: string }) => (
className: 'search-icon', className: 'search-icon',
})` })`
position: absolute; position: absolute;
left: ${props => props.theme.spacing.unit * 4}px; left: ${(props) => props.theme.spacing.unit * 4}px;
height: 1.8em; height: 1.8em;
width: 0.9em; width: 0.9em;
path { path {
fill: ${props => props.theme.sidebar.textColor}; fill: ${(props) => props.theme.sidebar.textColor};
} }
`; `;
export const SearchResultsBox = styled.div` export const SearchResultsBox = styled.div`
padding: ${props => props.theme.spacing.unit}px 0; padding: ${(props) => props.theme.spacing.unit}px 0;
background-color: ${({ theme }) => darken(0.05, theme.sidebar.backgroundColor)}}; background-color: ${({ theme }) => darken(0.05, theme.sidebar.backgroundColor)}};
color: ${props => props.theme.sidebar.textColor}; color: ${(props) => props.theme.sidebar.textColor};
min-height: 150px; min-height: 150px;
max-height: 250px; max-height: 250px;
border-top: ${({ theme }) => darken(0.1, theme.sidebar.backgroundColor)}}; border-top: ${({ theme }) => darken(0.1, theme.sidebar.backgroundColor)}};
@ -89,9 +89,9 @@ export const SearchResultsBox = styled.div`
export const ClearIcon = styled.i` export const ClearIcon = styled.i`
position: absolute; position: absolute;
display: inline-block; display: inline-block;
width: ${props => props.theme.spacing.unit * 2}px; width: ${(props) => props.theme.spacing.unit * 2}px;
text-align: center; text-align: center;
right: ${props => props.theme.spacing.unit * 4}px; right: ${(props) => props.theme.spacing.unit * 4}px;
line-height: 2em; line-height: 2em;
vertical-align: middle; vertical-align: middle;
margin-right: 2px; margin-right: 2px;

View File

@ -49,7 +49,7 @@ export class OAuthFlow extends React.PureComponent<OAuthFlowProps> {
<strong> Scopes: </strong> <strong> Scopes: </strong>
</div> </div>
<ul> <ul>
{Object.keys(flow!.scopes || {}).map(scope => ( {Object.keys(flow!.scopes || {}).map((scope) => (
<li key={scope}> <li key={scope}>
<code>{scope}</code> - <Markdown inline={true} source={flow!.scopes[scope] || ''} /> <code>{scope}</code> - <Markdown inline={true} source={flow!.scopes[scope] || ''} />
</li> </li>
@ -67,7 +67,7 @@ export interface SecurityDefsProps {
export class SecurityDefs extends React.PureComponent<SecurityDefsProps> { export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
render() { render() {
return this.props.securitySchemes.schemes.map(scheme => ( return this.props.securitySchemes.schemes.map((scheme) => (
<Section id={scheme.sectionId} key={scheme.id}> <Section id={scheme.sectionId} key={scheme.id}>
<Row> <Row>
<MiddlePanel> <MiddlePanel>
@ -115,7 +115,7 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
</td> </td>
</tr> </tr>
) : scheme.flows ? ( ) : scheme.flows ? (
Object.keys(scheme.flows).map(type => ( Object.keys(scheme.flows).map((type) => (
<OAuthFlow key={type} type={type} flow={scheme.flows[type]} /> <OAuthFlow key={type} type={type} flow={scheme.flows[type]} />
)) ))
) : null} ) : null}

View File

@ -12,7 +12,7 @@ export class SelectOnClick extends React.PureComponent {
const { children } = this.props; const { children } = this.props;
return ( return (
<div <div
ref={el => (this.child = el)} ref={(el) => (this.child = el)}
onClick={this.selectElement} onClick={this.selectElement}
onFocus={this.selectElement} onFocus={this.selectElement}
tabIndex={0} tabIndex={0}

View File

@ -46,7 +46,7 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
}); });
}; };
private saveScrollUpdate = upd => { private saveScrollUpdate = (upd) => {
this._updateScroll = upd; this._updateScroll = upd;
}; };
} }

View File

@ -9,8 +9,8 @@ export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
}))<{ type: string }>` }))<{ type: string }>`
width: 9ex; width: 9ex;
display: inline-block; display: inline-block;
height: ${props => props.theme.typography.code.fontSize}; height: ${(props) => props.theme.typography.code.fontSize};
line-height: ${props => props.theme.typography.code.fontSize}; line-height: ${(props) => props.theme.typography.code.fontSize};
background-color: #333; background-color: #333;
border-radius: 3px; border-radius: 3px;
background-repeat: no-repeat; background-repeat: no-repeat;
@ -26,43 +26,43 @@ export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
margin-top: 2px; margin-top: 2px;
&.get { &.get {
background-color: ${props => props.theme.colors.http.get}; background-color: ${(props) => props.theme.colors.http.get};
} }
&.post { &.post {
background-color: ${props => props.theme.colors.http.post}; background-color: ${(props) => props.theme.colors.http.post};
} }
&.put { &.put {
background-color: ${props => props.theme.colors.http.put}; background-color: ${(props) => props.theme.colors.http.put};
} }
&.options { &.options {
background-color: ${props => props.theme.colors.http.options}; background-color: ${(props) => props.theme.colors.http.options};
} }
&.patch { &.patch {
background-color: ${props => props.theme.colors.http.patch}; background-color: ${(props) => props.theme.colors.http.patch};
} }
&.delete { &.delete {
background-color: ${props => props.theme.colors.http.delete}; background-color: ${(props) => props.theme.colors.http.delete};
} }
&.basic { &.basic {
background-color: ${props => props.theme.colors.http.basic}; background-color: ${(props) => props.theme.colors.http.basic};
} }
&.link { &.link {
background-color: ${props => props.theme.colors.http.link}; background-color: ${(props) => props.theme.colors.http.link};
} }
&.head { &.head {
background-color: ${props => props.theme.colors.http.head}; background-color: ${(props) => props.theme.colors.http.head};
} }
&.hook { &.hook {
background-color: ${props => props.theme.colors.primary.main}; background-color: ${(props) => props.theme.colors.primary.main};
} }
`; `;
@ -84,7 +84,7 @@ export const MenuItemUl = styled.ul<{ expanded: boolean }>`
font-size: 0.929em; font-size: 0.929em;
} }
${props => (props.expanded ? '' : 'display: none;')}; ${(props) => (props.expanded ? '' : 'display: none;')};
`; `;
export const MenuItemLi = styled.li<{ depth: number }>` export const MenuItemLi = styled.li<{ depth: number }>`
@ -92,7 +92,7 @@ export const MenuItemLi = styled.li<{ depth: number }>`
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
padding: 0; padding: 0;
${props => (props.depth === 0 ? 'margin-top: 15px' : '')}; ${(props) => (props.depth === 0 ? 'margin-top: 15px' : '')};
`; `;
export const menuItemDepth = { export const menuItemDepth = {
@ -102,17 +102,17 @@ export const menuItemDepth = {
font-size: 0.8em; font-size: 0.8em;
padding-bottom: 0; padding-bottom: 0;
cursor: default; cursor: default;
color: ${props => props.theme.sidebar.textColor}; color: ${(props) => props.theme.sidebar.textColor};
`, `,
1: css` 1: css`
font-size: 0.929em; font-size: 0.929em;
text-transform: ${({ theme }) => theme.sidebar.level1Items.textTransform}; text-transform: ${({ theme }) => theme.sidebar.level1Items.textTransform};
&:hover { &:hover {
color: ${props => props.theme.sidebar.activeTextColor}; color: ${(props) => props.theme.sidebar.activeTextColor};
} }
`, `,
2: css` 2: css`
color: ${props => props.theme.sidebar.textColor}; color: ${(props) => props.theme.sidebar.textColor};
`, `,
}; };
@ -130,22 +130,22 @@ export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
}), }),
}))<MenuItemLabelType>` }))<MenuItemLabelType>`
cursor: pointer; cursor: pointer;
color: ${props => color: ${(props) =>
props.active ? props.theme.sidebar.activeTextColor : props.theme.sidebar.textColor}; props.active ? props.theme.sidebar.activeTextColor : props.theme.sidebar.textColor};
margin: 0; margin: 0;
padding: 12.5px ${props => props.theme.spacing.unit * 4}px; padding: 12.5px ${(props) => props.theme.spacing.unit * 4}px;
${({ depth, type, theme }) => ${({ depth, type, theme }) =>
(type === 'section' && depth > 1 && 'padding-left: ' + theme.spacing.unit * 8 + 'px;') || ''} (type === 'section' && depth > 1 && 'padding-left: ' + theme.spacing.unit * 8 + 'px;') || ''}
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
font-family: ${props => props.theme.typography.headings.fontFamily}; font-family: ${(props) => props.theme.typography.headings.fontFamily};
${props => menuItemDepth[props.depth]}; ${(props) => menuItemDepth[props.depth]};
background-color: ${props => (props.active ? menuItemActiveBg(props.depth, props) : '')}; background-color: ${(props) => (props.active ? menuItemActiveBg(props.depth, props) : '')};
${props => (props.deprecated && deprecatedCss) || ''}; ${(props) => (props.deprecated && deprecatedCss) || ''};
&:hover { &:hover {
background-color: ${props => menuItemActiveBg(props.depth, props)}; background-color: ${(props) => menuItemActiveBg(props.depth, props)};
} }
${ShelfIcon} { ${ShelfIcon} {
@ -160,7 +160,7 @@ export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
export const MenuItemTitle = styled.span<{ width?: string }>` export const MenuItemTitle = styled.span<{ width?: string }>`
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
width: ${props => (props.width ? props.width : 'auto')}; width: ${(props) => (props.width ? props.width : 'auto')};
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
`; `;

View File

@ -62,5 +62,5 @@ const ChevronContainer = styled.div`
align-self: center; align-self: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: ${props => props.theme.colors.primary.main}; color: ${(props) => props.theme.colors.primary.main};
`; `;

View File

@ -26,8 +26,8 @@ export interface StickySidebarState {
const stickyfill = Stickyfill && Stickyfill(); const stickyfill = Stickyfill && Stickyfill();
const StyledStickySidebar = styled.div<{ open?: boolean }>` const StyledStickySidebar = styled.div<{ open?: boolean }>`
width: ${props => props.theme.sidebar.width}; width: ${(props) => props.theme.sidebar.width};
background-color: ${props => props.theme.sidebar.backgroundColor}; background-color: ${(props) => props.theme.sidebar.backgroundColor};
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -45,7 +45,7 @@ const StyledStickySidebar = styled.div<{ open?: boolean }>`
z-index: 20; z-index: 20;
width: 100%; width: 100%;
background: ${({ theme }) => theme.sidebar.backgroundColor}; background: ${({ theme }) => theme.sidebar.backgroundColor};
display: ${props => (props.open ? 'flex' : 'none')}; display: ${(props) => (props.open ? 'flex' : 'none')};
`}; `};
@media print { @media print {
@ -57,7 +57,7 @@ const FloatingButton = styled.div`
outline: none; outline: none;
user-select: none; user-select: none;
background-color: #f2f2f2; background-color: #f2f2f2;
color: ${props => props.theme.colors.primary.main}; color: ${(props) => props.theme.colors.primary.main};
display: none; display: none;
cursor: pointer; cursor: pointer;
position: fixed; position: fixed;
@ -134,7 +134,7 @@ export class StickyResponsiveSidebar extends React.Component<
height: `calc(100vh - ${top})`, height: `calc(100vh - ${top})`,
}} }}
// tslint:disable-next-line // tslint:disable-next-line
ref={el => { ref={(el) => {
this.stickyElement = el as any; this.stickyElement = el as any;
}} }}
> >

View File

@ -44,7 +44,7 @@ export function StoreBuilder(props: StoreBuilderProps) {
setResolvedSpec(resolved); setResolvedSpec(resolved);
} }
load(); load();
}, [spec, specUrl]) }, [spec, specUrl]);
const store = React.useMemo(() => { const store = React.useMemo(() => {
if (!resolvedSpec) return null; if (!resolvedSpec) return null;
@ -62,7 +62,7 @@ export function StoreBuilder(props: StoreBuilderProps) {
if (store && onLoaded) { if (store && onLoaded) {
onLoaded(); onLoaded();
} }
}, [store, onLoaded]) }, [store, onLoaded]);
return children({ return children({
loading: !store, loading: !store,

View File

@ -10,14 +10,15 @@ const options = new RedocNormalizedOptions({});
describe('Components', () => { describe('Components', () => {
describe('SecurityRequirement', () => { describe('SecurityRequirement', () => {
describe('SecurityRequirement', () => { describe('SecurityRequirement', () => {
it('should render \'None\' when empty object in security open api', () => { it("should render 'None' when empty object in security open api", () => {
const parser = new OpenAPIParser({ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} }, const parser = new OpenAPIParser(
{ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} },
undefined, undefined,
options, options,
); );
const securityRequirement = new SecurityRequirementModel({}, parser); const securityRequirement = new SecurityRequirementModel({}, parser);
const securityElement = shallow( const securityElement = shallow(
<SecurityRequirement key={1} security={securityRequirement} /> <SecurityRequirement key={1} security={securityRequirement} />,
).getElement(); ).getElement();
expect(securityElement.props.children.type.target).toEqual('span'); expect(securityElement.props.children.type.target).toEqual('span');
expect(securityElement.props.children.props.children).toEqual('None'); expect(securityElement.props.children.props.children).toEqual('None');

View File

@ -89,7 +89,7 @@ export class AppStore {
this.search.indexItems(this.menu.items); this.search.indexItems(this.menu.items);
} }
this.disposer = observe(this.menu, 'activeItemIdx', change => { this.disposer = observe(this.menu, 'activeItemIdx', (change) => {
this.updateMarkOnMenu(change.newValue as number); this.updateMarkOnMenu(change.newValue as number);
}); });
} }

View File

@ -121,10 +121,7 @@ export class MarkdownRenderer {
prevRegexp = regexp; prevRegexp = regexp;
prevPos = currentPos; prevPos = currentPos;
} }
prevHeading.description = rawText prevHeading.description = rawText.substring(prevPos).replace(prevRegexp, '').trim();
.substring(prevPos)
.replace(prevRegexp, '')
.trim();
} }
headingRule = ( headingRule = (

View File

@ -37,7 +37,7 @@ export class MarkerService {
if (!term && !this.prevTerm) { if (!term && !this.prevTerm) {
return; return;
} }
this.map.forEach(val => { this.map.forEach((val) => {
val.unmark(); val.unmark();
val.mark(term || this.prevTerm); val.mark(term || this.prevTerm);
}); });
@ -45,7 +45,7 @@ export class MarkerService {
} }
unmark() { unmark() {
this.map.forEach(val => val.unmark()); this.map.forEach((val) => val.unmark());
this.prevTerm = ''; this.prevTerm = '';
} }
} }

View File

@ -86,7 +86,7 @@ export class MenuBuilder {
} }
const mapHeadingsDeep = (_parent, items, depth = 1) => const mapHeadingsDeep = (_parent, items, depth = 1) =>
items.map(heading => { items.map((heading) => {
const group = new GroupModel('section', heading, _parent); const group = new GroupModel('section', heading, _parent);
group.depth = depth; group.depth = depth;
if (heading.items) { if (heading.items) {
@ -149,7 +149,7 @@ export class MenuBuilder {
tagNames = group.tags; tagNames = group.tags;
} }
const tags = tagNames.map(tagName => { const tags = tagNames.map((tagName) => {
if (!tagsMap[tagName]) { if (!tagsMap[tagName]) {
console.warn(`Non-existing tag "${tagName}" is added to the group "${group!.name}"`); console.warn(`Non-existing tag "${tagName}" is added to the group "${group!.name}"`);
return null; return null;

View File

@ -144,12 +144,12 @@ export class MenuStore {
} }
let item: IMenuItem | undefined; let item: IMenuItem | undefined;
item = this.flatItems.find(i => i.id === id); item = this.flatItems.find((i) => i.id === id);
if (item) { if (item) {
this.activateAndScroll(item, false); this.activateAndScroll(item, false);
} else { } else {
if (id.startsWith(SECURITY_SCHEMES_SECTION_PREFIX)) { if (id.startsWith(SECURITY_SCHEMES_SECTION_PREFIX)) {
item = this.flatItems.find(i => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id)); item = this.flatItems.find((i) => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id));
this.activate(item); this.activate(item);
} }
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`); this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
@ -185,7 +185,7 @@ export class MenuStore {
} }
getItemById = (id: string) => { getItemById = (id: string) => {
return this.flatItems.find(item => item.id === id); return this.flatItems.find((item) => item.id === id);
}; };
/** /**

View File

@ -193,7 +193,10 @@ export class OpenAPIParser {
if (keys.length === 0) { if (keys.length === 0) {
return resolved; return resolved;
} }
if (mergeAsAllOf && keys.some((k) => k !== 'description' && k !== 'title' && k !== 'externalDocs')) { if (
mergeAsAllOf &&
keys.some((k) => k !== 'description' && k !== 'title' && k !== 'externalDocs')
) {
return { return {
allOf: [rest, resolved], allOf: [rest, resolved],
}; };

View File

@ -23,8 +23,8 @@ export class SearchStore<T> {
searchWorker = getWorker(); searchWorker = getWorker();
indexItems(groups: Array<IMenuItem | OperationModel>) { indexItems(groups: Array<IMenuItem | OperationModel>) {
const recurse = items => { const recurse = (items) => {
items.forEach(group => { items.forEach((group) => {
if (group.type !== 'group') { if (group.type !== 'group') {
this.add(group.name, group.description || '', group.id); this.add(group.name, group.description || '', group.id);
} }
@ -59,7 +59,7 @@ export class SearchStore<T> {
fromExternalJS(path?: string, exportName?: string) { fromExternalJS(path?: string, exportName?: string) {
if (path && exportName) { if (path && exportName) {
this.searchWorker.fromExternalJS(path, exportName) this.searchWorker.fromExternalJS(path, exportName);
} }
} }
} }

View File

@ -47,14 +47,14 @@ function initEmpty() {
builder.pipeline.add(lunr.trimmer, lunr.stopWordFilter, lunr.stemmer); builder.pipeline.add(lunr.trimmer, lunr.stopWordFilter, lunr.stemmer);
index = new Promise(resolve => { index = new Promise((resolve) => {
resolveIndex = resolve; resolveIndex = resolve;
}); });
} }
initEmpty(); initEmpty();
const expandTerm = term => '*' + lunr.stemmer(new lunr.Token(term, {})) + '*'; const expandTerm = (term) => '*' + lunr.stemmer(new lunr.Token(term, {})) + '*';
export function add<T>(title: string, description: string, meta?: T) { export function add<T>(title: string, description: string, meta?: T) {
const ref = store.push(meta) - 1; const ref = store.push(meta) - 1;
@ -104,11 +104,11 @@ export async function search<Meta = string>(
return []; return [];
} }
let searchResults = (await index).query(t => { let searchResults = (await index).query((t) => {
q.trim() q.trim()
.toLowerCase() .toLowerCase()
.split(/\s+/) .split(/\s+/)
.forEach(term => { .forEach((term) => {
if (term.length === 1) return; if (term.length === 1) return;
const exp = expandTerm(term); const exp = expandTerm(term);
t.term(exp, {}); t.term(exp, {});
@ -119,5 +119,5 @@ export async function search<Meta = string>(
searchResults = searchResults.slice(0, limit); searchResults = searchResults.slice(0, limit);
} }
return searchResults.map(res => ({ meta: store[res.ref], score: res.score })); return searchResults.map((res) => ({ meta: store[res.ref], score: res.score }));
} }

View File

@ -28,7 +28,10 @@ export class SpecStore {
this.externalDocs = this.parser.spec.externalDocs; this.externalDocs = this.parser.spec.externalDocs;
this.contentItems = MenuBuilder.buildStructure(this.parser, this.options); this.contentItems = MenuBuilder.buildStructure(this.parser, this.options);
this.securitySchemes = new SecuritySchemesModel(this.parser); this.securitySchemes = new SecuritySchemesModel(this.parser);
const webhookPath: Referenced<OpenAPIPath> = {...this.parser?.spec?.['x-webhooks'], ...this.parser?.spec.webhooks}; const webhookPath: Referenced<OpenAPIPath> = {
...this.parser?.spec?.['x-webhooks'],
...this.parser?.spec.webhooks,
};
this.webhooks = new WebhookModel(this.parser, options, webhookPath); this.webhooks = new WebhookModel(this.parser, options, webhookPath);
} }
} }

View File

@ -26,6 +26,5 @@ describe('Models', () => {
expect(parser.shallowDeref(schemaOrRef)).toMatchSnapshot(); expect(parser.shallowDeref(schemaOrRef)).toMatchSnapshot();
}); });
}); });
}); });

View File

@ -54,8 +54,8 @@ describe('Models', () => {
license: { license: {
name: 'MIT', name: 'MIT',
identifier: 'MIT', identifier: 'MIT',
url: 'https://opensource.org/licenses/MIT' url: 'https://opensource.org/licenses/MIT',
} },
}, },
} as any; } as any;

View File

@ -19,7 +19,6 @@ describe('Models', () => {
expect(contentItems[0].id).toEqual('tag/pet'); expect(contentItems[0].id).toEqual('tag/pet');
expect(contentItems[0].name).toEqual('pet'); expect(contentItems[0].name).toEqual('pet');
expect(contentItems[0].type).toEqual('tag'); expect(contentItems[0].type).toEqual('tag');
}); });
}); });
}); });

View File

@ -41,8 +41,8 @@ export class ExampleModel {
return externalExamplesCache[this.externalValueUrl]; return externalExamplesCache[this.externalValueUrl];
} }
externalExamplesCache[this.externalValueUrl] = fetch(this.externalValueUrl).then(res => { externalExamplesCache[this.externalValueUrl] = fetch(this.externalValueUrl).then((res) => {
return res.text().then(txt => { return res.text().then((txt) => {
if (!res.ok) { if (!res.ok) {
return Promise.reject(new Error(txt)); return Promise.reject(new Error(txt));
} }

View File

@ -31,7 +31,7 @@ export class MediaContentModel {
if (options.unstable_ignoreMimeParameters) { if (options.unstable_ignoreMimeParameters) {
info = mergeSimilarMediaTypes(info); info = mergeSimilarMediaTypes(info);
} }
this.mediaTypes = Object.keys(info).map(name => { this.mediaTypes = Object.keys(info).map((name) => {
const mime = info[name]; const mime = info[name];
// reset deref cache just in case something is left there // reset deref cache just in case something is left there
parser.resetVisited(); parser.resetVisited();
@ -54,6 +54,6 @@ export class MediaContentModel {
} }
get hasSample(): boolean { get hasSample(): boolean {
return this.mediaTypes.filter(mime => !!mime.examples).length > 0; return this.mediaTypes.filter((mime) => !!mime.examples).length > 0;
} }
} }

View File

@ -34,7 +34,7 @@ export class MediaTypeModel {
if (info.examples !== undefined) { if (info.examples !== undefined) {
this.examples = mapValues( this.examples = mapValues(
info.examples, info.examples,
example => new ExampleModel(parser, example, name, info.encoding), (example) => new ExampleModel(parser, example, name, info.encoding),
); );
} else if (info.example !== undefined) { } else if (info.example !== undefined) {
this.examples = { this.examples = {

View File

@ -173,7 +173,8 @@ export class OperationModel implements IMenuItem {
@memoize @memoize
get requestBody() { get requestBody() {
return ( return (
this.operationSpec.requestBody && new RequestBodyModel({ this.operationSpec.requestBody &&
new RequestBodyModel({
parser: this.parser, parser: this.parser,
infoOrRef: this.operationSpec.requestBody, infoOrRef: this.operationSpec.requestBody,
options: this.options, options: this.options,

View File

@ -9,7 +9,7 @@ type RequestBodyProps = {
infoOrRef: Referenced<OpenAPIRequestBody>; infoOrRef: Referenced<OpenAPIRequestBody>;
options: RedocNormalizedOptions; options: RedocNormalizedOptions;
isEvent: boolean; isEvent: boolean;
} };
export class RequestBodyModel { export class RequestBodyModel {
description: string; description: string;

View File

@ -9,13 +9,13 @@ import { FieldModel } from './Field';
import { MediaContentModel } from './MediaContent'; import { MediaContentModel } from './MediaContent';
type ResponseProps = { type ResponseProps = {
parser: OpenAPIParser, parser: OpenAPIParser;
code: string, code: string;
defaultAsError: boolean, defaultAsError: boolean;
infoOrRef: Referenced<OpenAPIResponse>, infoOrRef: Referenced<OpenAPIResponse>;
options: RedocNormalizedOptions, options: RedocNormalizedOptions;
isEvent: boolean, isEvent: boolean;
} };
export class ResponseModel { export class ResponseModel {
@observable @observable
@ -54,7 +54,7 @@ export class ResponseModel {
const headers = info.headers; const headers = info.headers;
if (headers !== undefined) { if (headers !== undefined) {
this.headers = Object.keys(headers).map(name => { this.headers = Object.keys(headers).map((name) => {
const header = headers[name]; const header = headers[name];
return new FieldModel(parser, { ...header, name }, '', options); return new FieldModel(parser, { ...header, name }, '', options);
}); });

View File

@ -134,7 +134,10 @@ export class SchemaModel {
this.maxItems = schema.maxItems; this.maxItems = schema.maxItems;
if (!!schema.nullable || schema['x-nullable']) { if (!!schema.nullable || schema['x-nullable']) {
if (Array.isArray(this.type) && !this.type.some((value) => value === null || value === 'null')) { if (
Array.isArray(this.type) &&
!this.type.some((value) => value === null || value === 'null')
) {
this.type = [...this.type, 'null']; this.type = [...this.type, 'null'];
} else if (!Array.isArray(this.type) && (this.type !== null || this.type !== 'null')) { } else if (!Array.isArray(this.type) && (this.type !== null || this.type !== 'null')) {
this.type = [this.type, 'null']; this.type = [this.type, 'null'];
@ -142,7 +145,7 @@ export class SchemaModel {
} }
this.displayType = Array.isArray(this.type) this.displayType = Array.isArray(this.type)
? this.type.map(item => item === null ? 'null' : item).join(' or ') ? this.type.map((item) => (item === null ? 'null' : item)).join(' or ')
: this.type; : this.type;
if (this.isCircular) { if (this.isCircular) {
@ -194,9 +197,8 @@ export class SchemaModel {
this.enum = this.items.enum; this.enum = this.items.enum;
} }
if (Array.isArray(this.type)) { if (Array.isArray(this.type)) {
const filteredType = this.type.filter(item => item !== 'array'); const filteredType = this.type.filter((item) => item !== 'array');
if (filteredType.length) if (filteredType.length) this.displayType += ` or ${filteredType.join(' or ')}`;
this.displayType += ` or ${filteredType.join(' or ')}`;
} }
} }
@ -215,7 +217,7 @@ export class SchemaModel {
const title = const title =
isNamedDefinition(variant.$ref) && !merged.title isNamedDefinition(variant.$ref) && !merged.title
? JsonPointer.baseName(variant.$ref) ? JsonPointer.baseName(variant.$ref)
: `${(merged.title || '')}${(merged.const && JSON.stringify(merged.const)) || ''}`; : `${merged.title || ''}${(merged.const && JSON.stringify(merged.const)) || ''}`;
const schema = new SchemaModel( const schema = new SchemaModel(
parser, parser,

View File

@ -15,7 +15,7 @@ export class SecurityRequirementModel {
const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {}; const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {};
this.schemes = Object.keys(requirement || {}) this.schemes = Object.keys(requirement || {})
.map(id => { .map((id) => {
const scheme = parser.deref(schemes[id]); const scheme = parser.deref(schemes[id]);
const scopes = requirement[id] || []; const scopes = requirement[id] || [];
@ -31,6 +31,6 @@ export class SecurityRequirementModel {
scopes, scopes,
}; };
}) })
.filter(scheme => scheme !== undefined) as SecurityScheme[]; .filter((scheme) => scheme !== undefined) as SecurityScheme[];
} }
} }

View File

@ -60,7 +60,7 @@ export class SecuritySchemesModel {
constructor(parser: OpenAPIParser) { constructor(parser: OpenAPIParser) {
const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {}; const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {};
this.schemes = Object.keys(schemes).map( this.schemes = Object.keys(schemes).map(
name => new SecuritySchemeModel(parser, name, schemes[name]), (name) => new SecuritySchemeModel(parser, name, schemes[name]),
); );
} }
} }

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { hydrate as hydrateComponent, render } from 'react-dom'; import { hydrate as hydrateComponent, render } from 'react-dom';
import { configure } from "mobx" import { configure } from 'mobx';
import { Redoc, RedocStandalone } from './components/'; import { Redoc, RedocStandalone } from './components/';
import { AppStore, StoreState } from './services/AppStore'; import { AppStore, StoreState } from './services/AppStore';
@ -8,8 +8,8 @@ import { debugTime, debugTimeEnd } from './utils/debug';
import { querySelector } from './utils/dom'; import { querySelector } from './utils/dom';
configure({ configure({
useProxies: 'ifavailable' useProxies: 'ifavailable',
}) });
export { Redoc, AppStore } from '.'; export { Redoc, AppStore } from '.';

View File

@ -15,7 +15,7 @@ const {
export const media = { export const media = {
lessThan(breakpoint, print?: boolean, extra?: string) { lessThan(breakpoint, print?: boolean, extra?: string) {
return (...args) => css` return (...args) => css`
@media ${print ? 'print, ' : ''} screen and (max-width: ${props => @media ${print ? 'print, ' : ''} screen and (max-width: ${(props) =>
props.theme.breakpoints[breakpoint]}) ${extra || ''} { props.theme.breakpoints[breakpoint]}) ${extra || ''} {
${(css as any)(...args)}; ${(css as any)(...args)};
} }
@ -24,7 +24,7 @@ export const media = {
greaterThan(breakpoint) { greaterThan(breakpoint) {
return (...args) => css` return (...args) => css`
@media (min-width: ${props => props.theme.breakpoints[breakpoint]}) { @media (min-width: ${(props) => props.theme.breakpoints[breakpoint]}) {
${(css as any)(...args)}; ${(css as any)(...args)};
} }
`; `;
@ -32,9 +32,9 @@ export const media = {
between(firstBreakpoint, secondBreakpoint) { between(firstBreakpoint, secondBreakpoint) {
return (...args) => css` return (...args) => css`
@media (min-width: ${props => @media (min-width: ${(props) => props.theme.breakpoints[firstBreakpoint]}) and (max-width: ${(
props.theme.breakpoints[firstBreakpoint]}) and (max-width: ${props => props,
props.theme.breakpoints[secondBreakpoint]}) { ) => props.theme.breakpoints[secondBreakpoint]}) {
${(css as any)(...args)}; ${(css as any)(...args)};
} }
`; `;
@ -45,7 +45,7 @@ export { css, createGlobalStyle, keyframes, ThemeProvider };
export default styled; export default styled;
export function extensionsHook(styledName: string) { export function extensionsHook(styledName: string) {
return props => { return (props) => {
if (!props.theme.extensionsHook) { if (!props.theme.extensionsHook) {
return; return;
} }

View File

@ -84,21 +84,21 @@ const defaultTheme: ThemeInterface = {
}, },
}, },
schema: { schema: {
linesColor: theme => linesColor: (theme) =>
lighten( lighten(
theme.colors.tonalOffset, theme.colors.tonalOffset,
desaturate(theme.colors.tonalOffset, theme.colors.primary.main), desaturate(theme.colors.tonalOffset, theme.colors.primary.main),
), ),
defaultDetailsWidth: '75%', defaultDetailsWidth: '75%',
typeNameColor: theme => theme.colors.text.secondary, typeNameColor: (theme) => theme.colors.text.secondary,
typeTitleColor: theme => theme.schema.typeNameColor, typeTitleColor: (theme) => theme.schema.typeNameColor,
requireLabelColor: theme => theme.colors.error.main, requireLabelColor: (theme) => theme.colors.error.main,
labelsTextSize: '0.9em', labelsTextSize: '0.9em',
nestingSpacing: '1em', nestingSpacing: '1em',
nestedBackground: '#fafafa', nestedBackground: '#fafafa',
arrow: { arrow: {
size: '1.1em', size: '1.1em',
color: theme => theme.colors.text.secondary, color: (theme) => theme.colors.text.secondary,
}, },
}, },
typography: { typography: {
@ -134,7 +134,7 @@ const defaultTheme: ThemeInterface = {
width: '260px', width: '260px',
backgroundColor: '#fafafa', backgroundColor: '#fafafa',
textColor: '#333333', textColor: '#333333',
activeTextColor: theme => activeTextColor: (theme) =>
theme.sidebar.textColor !== defaultTheme.sidebar!.textColor theme.sidebar.textColor !== defaultTheme.sidebar!.textColor
? theme.sidebar.textColor ? theme.sidebar.textColor
: theme.colors.primary.main, : theme.colors.primary.main,
@ -146,7 +146,7 @@ const defaultTheme: ThemeInterface = {
}, },
arrow: { arrow: {
size: '1.5em', size: '1.5em',
color: theme => theme.sidebar.textColor, color: (theme) => theme.sidebar.textColor,
}, },
}, },
logo: { logo: {
@ -170,7 +170,7 @@ export function resolveTheme(theme: ThemeInterface): ResolvedThemeInterface {
const resolvedValues = {}; const resolvedValues = {};
let counter = 0; let counter = 0;
const setProxy = (obj, path: string) => { const setProxy = (obj, path: string) => {
Object.keys(obj).forEach(k => { Object.keys(obj).forEach((k) => {
const currentPath = (path ? path + '.' : '') + k; const currentPath = (path ? path + '.' : '') + k;
const val = obj[k]; const val = obj[k];
if (typeof val === 'function') { if (typeof val === 'function') {

View File

@ -11,7 +11,9 @@ describe('#loadAndBundleSpec', () => {
}); });
it('should load And Bundle Spec demo/openapi-3-1.yaml', async () => { it('should load And Bundle Spec demo/openapi-3-1.yaml', async () => {
const spec = yaml.load(readFileSync(resolve(__dirname, '../../../demo/openapi-3-1.yaml'), 'utf-8')); const spec = yaml.load(
readFileSync(resolve(__dirname, '../../../demo/openapi-3-1.yaml'), 'utf-8'),
);
const bundledSpec = await loadAndBundleSpec(spec); const bundledSpec = await loadAndBundleSpec(spec);
expect(bundledSpec).toMatchSnapshot(); expect(bundledSpec).toMatchSnapshot();
}); });

View File

@ -103,7 +103,7 @@ describe('Utils', () => {
it('Should return pathName if no summary, operationId, description', () => { it('Should return pathName if no summary, operationId, description', () => {
const operation = { const operation = {
pathName: '/sandbox/test' pathName: '/sandbox/test',
}; };
expect(getOperationSummary(operation as any)).toBe('/sandbox/test'); expect(getOperationSummary(operation as any)).toBe('/sandbox/test');
}); });
@ -141,9 +141,9 @@ describe('Utils', () => {
object: ['maxProperties', 'minProperties', 'required', 'additionalProperties', 'properties'], object: ['maxProperties', 'minProperties', 'required', 'additionalProperties', 'properties'],
}; };
Object.keys(tests).forEach(name => { Object.keys(tests).forEach((name) => {
it(`Should detect ${name} if ${name} properties are present`, () => { it(`Should detect ${name} if ${name} properties are present`, () => {
tests[name].forEach(propName => { tests[name].forEach((propName) => {
expect( expect(
detectType({ detectType({
[propName]: 0, [propName]: 0,
@ -174,7 +174,7 @@ describe('Utils', () => {
expect(isPrimitiveType(schema)).toEqual(false); expect(isPrimitiveType(schema)).toEqual(false);
}); });
it('should return true for array contains object and schema hasn\'t properties', () => { it("should return true for array contains object and schema hasn't properties", () => {
const schema = { const schema = {
type: ['object', 'string'], type: ['object', 'string'],
}; };
@ -233,7 +233,7 @@ describe('Utils', () => {
items: { items: {
type: 'array', type: 'array',
items: { items: {
type: 'string' type: 'string',
}, },
}, },
}; };
@ -415,7 +415,7 @@ describe('Utils', () => {
min?: number, min?: number,
max?: number, max?: number,
multipleOf?: number, multipleOf?: number,
uniqueItems?: boolean uniqueItems?: boolean,
) => ({ type: 'array', minItems: min, maxItems: max, multipleOf, uniqueItems }); ) => ({ type: 'array', minItems: min, maxItems: max, multipleOf, uniqueItems });
it('should not have a humanized constraint without schema constraints', () => { it('should not have a humanized constraint without schema constraints', () => {
@ -455,9 +455,9 @@ describe('Utils', () => {
}); });
it('should have a humanized constraint when uniqueItems is set', () => { it('should have a humanized constraint when uniqueItems is set', () => {
expect(humanizeConstraints(itemConstraintSchema(undefined, undefined, undefined, true))).toContain( expect(
'unique', humanizeConstraints(itemConstraintSchema(undefined, undefined, undefined, true)),
); ).toContain('unique');
}); });
}); });
@ -656,11 +656,11 @@ describe('Utils', () => {
}, },
]; ];
testCases.forEach(locationTestGroup => { testCases.forEach((locationTestGroup) => {
describe(locationTestGroup.description, () => { describe(locationTestGroup.description, () => {
locationTestGroup.cases.forEach(valueTypeTestGroup => { locationTestGroup.cases.forEach((valueTypeTestGroup) => {
describe(valueTypeTestGroup.description, () => { describe(valueTypeTestGroup.description, () => {
valueTypeTestGroup.cases.forEach(testCase => { valueTypeTestGroup.cases.forEach((testCase) => {
it(`should serialize correctly when style is ${testCase.style} and explode is ${testCase.explode}`, () => { it(`should serialize correctly when style is ${testCase.style} and explode is ${testCase.explode}`, () => {
const parameter: OpenAPIParameter = { const parameter: OpenAPIParameter = {
name: locationTestGroup.name, name: locationTestGroup.name,

View File

@ -15,10 +15,10 @@ export function querySelector(selector: string): Element | null {
export function html2Str(html: string): string { export function html2Str(html: string): string {
return html return html
.split(/<[^>]+>/) .split(/<[^>]+>/)
.map(chunk => { .map((chunk) => {
return chunk.trim(); return chunk.trim();
}) })
.filter(trimmedChunk => { .filter((trimmedChunk) => {
return trimmedChunk.length > 0; return trimmedChunk.length > 0;
}) })
.join(' '); .join(' ');

View File

@ -50,7 +50,7 @@ export function flattenByProp<T extends object, P extends keyof T>(
for (const item of items) { for (const item of items) {
res.push(item); res.push(item);
if (item[prop]) { if (item[prop]) {
iterate((item[prop] as any) as T[]); iterate(item[prop] as any as T[]);
} }
} }
}; };

View File

@ -14,8 +14,8 @@ export async function loadAndBundleSpec(specUrlOrObject: object | string): Promi
const config = new Config({}); const config = new Config({});
const bundleOpts = { const bundleOpts = {
config, config,
base: IS_BROWSER ? window.location.href : process.cwd() base: IS_BROWSER ? window.location.href : process.cwd(),
} };
if (IS_BROWSER) { if (IS_BROWSER) {
config.resolve.http.customFetch = global.fetch; config.resolve.http.customFetch = global.fetch;
@ -24,13 +24,15 @@ export async function loadAndBundleSpec(specUrlOrObject: object | string): Promi
if (typeof specUrlOrObject === 'object' && specUrlOrObject !== null) { if (typeof specUrlOrObject === 'object' && specUrlOrObject !== null) {
bundleOpts['doc'] = { bundleOpts['doc'] = {
source: { absoluteRef: '' } as Source, source: { absoluteRef: '' } as Source,
parsed: specUrlOrObject parsed: specUrlOrObject,
} as Document } as Document;
} else { } else {
bundleOpts['ref'] = specUrlOrObject; bundleOpts['ref'] = specUrlOrObject;
} }
const { bundle: { parsed } } = await bundle(bundleOpts); const {
bundle: { parsed },
} = await bundle(bundleOpts);
return parsed.swagger !== undefined ? convertSwagger2OpenAPI(parsed) : parsed; return parsed.swagger !== undefined ? convertSwagger2OpenAPI(parsed) : parsed;
} }

View File

@ -3,7 +3,7 @@ const SENTINEL = {};
export function memoize<T>(target: any, name: string, descriptor: TypedPropertyDescriptor<T>) { export function memoize<T>(target: any, name: string, descriptor: TypedPropertyDescriptor<T>) {
if (typeof descriptor.value === 'function') { if (typeof descriptor.value === 'function') {
return (_memoizeMethod(target, name, descriptor) as any) as TypedPropertyDescriptor<T>; return _memoizeMethod(target, name, descriptor) as any as TypedPropertyDescriptor<T>;
} else if (typeof descriptor.get === 'function') { } else if (typeof descriptor.get === 'function') {
return _memoizeGetter(target, name, descriptor) as TypedPropertyDescriptor<T>; return _memoizeGetter(target, name, descriptor) as TypedPropertyDescriptor<T>;
} else { } else {

View File

@ -113,7 +113,10 @@ export function detectType(schema: OpenAPISchema): string {
return 'any'; return 'any';
} }
export function isPrimitiveType(schema: OpenAPISchema, type: string | string[] | undefined = schema.type) { export function isPrimitiveType(
schema: OpenAPISchema,
type: string | string[] | undefined = schema.type,
) {
if (schema.oneOf !== undefined || schema.anyOf !== undefined) { if (schema.oneOf !== undefined || schema.anyOf !== undefined) {
return false; return false;
} }
@ -122,7 +125,8 @@ export function isPrimitiveType(schema: OpenAPISchema, type: string | string[] |
const isArray = Array.isArray(type); const isArray = Array.isArray(type);
if (type === 'object' || (isArray && type?.includes('object'))) { if (type === 'object' || (isArray && type?.includes('object'))) {
isPrimitive = schema.properties !== undefined isPrimitive =
schema.properties !== undefined
? Object.keys(schema.properties).length === 0 ? Object.keys(schema.properties).length === 0
: schema.additionalProperties === undefined; : schema.additionalProperties === undefined;
} }
@ -144,10 +148,10 @@ export function isFormUrlEncoded(contentType: string): boolean {
function delimitedEncodeField(fieldVal: any, fieldName: string, delimiter: string): string { function delimitedEncodeField(fieldVal: any, fieldName: string, delimiter: string): string {
if (Array.isArray(fieldVal)) { if (Array.isArray(fieldVal)) {
return fieldVal.map(v => v.toString()).join(delimiter); return fieldVal.map((v) => v.toString()).join(delimiter);
} else if (typeof fieldVal === 'object') { } else if (typeof fieldVal === 'object') {
return Object.keys(fieldVal) return Object.keys(fieldVal)
.map(k => `${k}${delimiter}${fieldVal[k]}`) .map((k) => `${k}${delimiter}${fieldVal[k]}`)
.join(delimiter); .join(delimiter);
} else { } else {
return fieldName + '=' + fieldVal.toString(); return fieldName + '=' + fieldVal.toString();
@ -160,7 +164,7 @@ function deepObjectEncodeField(fieldVal: any, fieldName: string): string {
return ''; return '';
} else if (typeof fieldVal === 'object') { } else if (typeof fieldVal === 'object') {
return Object.keys(fieldVal) return Object.keys(fieldVal)
.map(k => `${fieldName}[${k}]=${fieldVal[k]}`) .map((k) => `${fieldName}[${k}]=${fieldVal[k]}`)
.join('&'); .join('&');
} else { } else {
console.warn('deepObject style cannot be used with non-object value:' + fieldVal.toString()); console.warn('deepObject style cannot be used with non-object value:' + fieldVal.toString());
@ -192,7 +196,7 @@ export function urlFormEncodePayload(
throw new Error('Payload must have fields: ' + payload.toString()); throw new Error('Payload must have fields: ' + payload.toString());
} else { } else {
return Object.keys(payload) return Object.keys(payload)
.map(fieldName => { .map((fieldName) => {
const fieldVal = payload[fieldName]; const fieldVal = payload[fieldName];
const { style = 'form', explode = true } = encoding[fieldName] || {}; const { style = 'form', explode = true } = encoding[fieldName] || {};
switch (style) { switch (style) {
@ -376,7 +380,7 @@ export function isNamedDefinition(pointer?: string): boolean {
export function getDefinitionName(pointer?: string): string | undefined { export function getDefinitionName(pointer?: string): string | undefined {
if (!pointer) return undefined; if (!pointer) return undefined;
const match = pointer.match(/^#\/components\/(schemas|pathItems)\/([^\/]+)$/); const match = pointer.match(/^#\/components\/(schemas|pathItems)\/([^\/]+)$/);
return match === null ? undefined : match[1] return match === null ? undefined : match[1];
} }
function humanizeMultipleOfConstraint(multipleOf: number | undefined): string | undefined { function humanizeMultipleOfConstraint(multipleOf: number | undefined): string | undefined {
@ -452,12 +456,14 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] {
let minimum = 0; let minimum = 0;
let maximum = 0; let maximum = 0;
if (schema.minimum) minimum = schema.minimum; if (schema.minimum) minimum = schema.minimum;
if (typeof schema.exclusiveMinimum === 'number') minimum = minimum <= schema.exclusiveMinimum ? minimum : schema.exclusiveMinimum; if (typeof schema.exclusiveMinimum === 'number')
minimum = minimum <= schema.exclusiveMinimum ? minimum : schema.exclusiveMinimum;
if (schema.maximum) maximum = schema.maximum; if (schema.maximum) maximum = schema.maximum;
if (typeof schema.exclusiveMaximum === 'number') maximum = maximum > schema.exclusiveMaximum ? maximum : schema.exclusiveMaximum; if (typeof schema.exclusiveMaximum === 'number')
maximum = maximum > schema.exclusiveMaximum ? maximum : schema.exclusiveMaximum;
numberRange = `[${minimum} .. ${maximum}]` numberRange = `[${minimum} .. ${maximum}]`;
} }
if (numberRange !== undefined) { if (numberRange !== undefined) {
@ -476,7 +482,7 @@ export function sortByRequired(fields: FieldModel[], order: string[] = []) {
const orderedFields: FieldModel[] = []; const orderedFields: FieldModel[] = [];
const unorderedFields: FieldModel[] = []; const unorderedFields: FieldModel[] = [];
fields.forEach(field => { fields.forEach((field) => {
if (field.required) { if (field.required) {
order.includes(field.name) ? orderedFields.push(field) : unorderedFields.push(field); order.includes(field.name) ? orderedFields.push(field) : unorderedFields.push(field);
} else { } else {
@ -504,13 +510,13 @@ export function mergeParams(
operationParams: Array<Referenced<OpenAPIParameter>> = [], operationParams: Array<Referenced<OpenAPIParameter>> = [],
): Array<Referenced<OpenAPIParameter>> { ): Array<Referenced<OpenAPIParameter>> {
const operationParamNames = {}; const operationParamNames = {};
operationParams.forEach(param => { operationParams.forEach((param) => {
param = parser.shallowDeref(param); param = parser.shallowDeref(param);
operationParamNames[param.name + '_' + param.in] = true; operationParamNames[param.name + '_' + param.in] = true;
}); });
// filter out path params overridden by operation ones with the same name // filter out path params overridden by operation ones with the same name
pathParams = pathParams.filter(param => { pathParams = pathParams.filter((param) => {
param = parser.shallowDeref(param); param = parser.shallowDeref(param);
return !operationParamNames[param.name + '_' + param.in]; return !operationParamNames[param.name + '_' + param.in];
}); });
@ -522,7 +528,7 @@ export function mergeSimilarMediaTypes(
types: Record<string, OpenAPIMediaType>, types: Record<string, OpenAPIMediaType>,
): Record<string, OpenAPIMediaType> { ): Record<string, OpenAPIMediaType> {
const mergedTypes = {}; const mergedTypes = {};
Object.keys(types).forEach(name => { Object.keys(types).forEach((name) => {
const mime = types[name]; const mime = types[name];
// ignore content type parameters (e.g. charset) and merge // ignore content type parameters (e.g. charset) and merge
const normalizedMimeName = name.split(';')[0].trim(); const normalizedMimeName = name.split(';')[0].trim();
@ -570,7 +576,7 @@ export function normalizeServers(
return resolveUrl(baseUrl, url); return resolveUrl(baseUrl, url);
} }
return servers.map(server => { return servers.map((server) => {
return { return {
...server, ...server,
url: normalizeUrl(server.url), url: normalizeUrl(server.url),
@ -588,7 +594,7 @@ export function setSecuritySchemePrefix(prefix: string) {
SECURITY_SCHEMES_SECTION_PREFIX = prefix; SECURITY_SCHEMES_SECTION_PREFIX = prefix;
} }
export const shortenHTTPVerb = verb => export const shortenHTTPVerb = (verb) =>
({ ({
delete: 'del', delete: 'del',
options: 'opts', options: 'opts',
@ -619,7 +625,7 @@ export function extractExtensions(
showExtensions: string[] | true, showExtensions: string[] | true,
): Record<string, any> { ): Record<string, any> {
return Object.keys(obj) return Object.keys(obj)
.filter(key => { .filter((key) => {
if (showExtensions === true) { if (showExtensions === true) {
return key.startsWith('x-') && !isRedocExtension(key); return key.startsWith('x-') && !isRedocExtension(key);
} }
@ -634,6 +640,6 @@ export function extractExtensions(
export function pluralizeType(displayType: string): string { export function pluralizeType(displayType: string): string {
return displayType return displayType
.split(' or ') .split(' or ')
.map(type => type.replace(/^(string|object|number|integer|array|boolean)s?( ?.*)/, '$1s$2')) .map((type) => type.replace(/^(string|object|number|integer|array|boolean)s?( ?.*)/, '$1s$2'))
.join(' or '); .join(' or ');
} }

View File

@ -17,7 +17,7 @@ function traverseComponent(root, fn) {
} }
export function filterPropsDeep<T extends object>(component: T, paths: string[]): T { export function filterPropsDeep<T extends object>(component: T, paths: string[]): T {
traverseComponent(component, comp => { traverseComponent(component, (comp) => {
if (comp.props) { if (comp.props) {
for (const path of paths) { for (const path of paths) {
if (has(comp.props, path)) { if (has(comp.props, path)) {