diff --git a/src/components/Fields/ArrayItemDetails.tsx b/src/components/Fields/ArrayItemDetails.tsx new file mode 100644 index 00000000..8d9c5e2a --- /dev/null +++ b/src/components/Fields/ArrayItemDetails.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { TypeFormat, TypeName, TypePrefix } from '../../common-elements/fields'; +import { ConstraintsView } from './FieldContstraints'; +import { Pattern } from './Pattern'; +import { SchemaModel } from '../../services'; +import styled from '../../styled-components'; + +export function ArrayItemDetails({ schema }: { schema: SchemaModel }) { + if (!schema || schema.type === 'string' && !schema.constraints.length) return null; + + return ( + + {schema.typePrefix} + {schema.displayType} + {schema.displayFormat && ( + {` <${schema.displayFormat}> `} + )} + + + {schema.items && } + + ); +} + + const ArrayItemDetailsStyled = styled.div` + padding-left: 7px; +`; diff --git a/src/components/Fields/EnumValues.tsx b/src/components/Fields/EnumValues.tsx index d10f9f69..43e5c73b 100644 --- a/src/components/Fields/EnumValues.tsx +++ b/src/components/Fields/EnumValues.tsx @@ -8,7 +8,7 @@ import { RedocRawOptions } from '../../services/RedocNormalizedOptions'; export interface EnumValuesProps { values: string[]; - type: string | string[]; + isArrayType: boolean; } export interface EnumValuesState { @@ -27,7 +27,7 @@ export class EnumValues extends React.PureComponent - {type === 'array' ? l('enumArray') : ''}{' '} + {isArrayType ? l('enumArray') : ''}{' '} {values.length === 1 ? l('enumSingleValue') : l('enum')}: {' '} {displayedItems.map((value, idx) => { diff --git a/src/components/Fields/Examples.tsx b/src/components/Fields/Examples.tsx new file mode 100644 index 00000000..690ecb9a --- /dev/null +++ b/src/components/Fields/Examples.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; + +import { FieldLabel, ExampleValue } from '../../common-elements/fields'; +import { getSerializedValue } from '../../utils'; + +import { l } from '../../services/Labels'; +import { FieldModel } from '../../services'; +import styled from '../../styled-components'; + +export function Examples({ field }: { field: FieldModel }) { + if (!field.examples) { + return null; + } + + return ( + <> + {l('examples')}: + + {Object.values(field.examples).map((example, idx) => { + return ( +
  • + {getSerializedValue(field, example.value)} - {example.summary || example.description} +
  • + ); + })} +
    + + ); +} + +const ExamplesList = styled.ul` + margin-top: 1em; + padding-left: 0; + list-style-position: inside; +`; diff --git a/src/components/Fields/FieldDetails.tsx b/src/components/Fields/FieldDetails.tsx index 1aad8c61..64a0f99a 100644 --- a/src/components/Fields/FieldDetails.tsx +++ b/src/components/Fields/FieldDetails.tsx @@ -1,22 +1,19 @@ import * as React from 'react'; import { - PatternLabel, RecursiveLabel, TypeFormat, TypeName, TypePrefix, TypeTitle, - ToggleButton, - FieldLabel, - ExampleValue, } from '../../common-elements/fields'; -import { serializeParameterValue } from '../../utils/openapi'; +import { getSerializedValue } from '../../utils'; import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { Markdown } from '../Markdown/Markdown'; import { EnumValues } from './EnumValues'; import { Extensions } from './Extensions'; import { FieldProps } from './Field'; +import { Examples } from './Examples'; import { ConstraintsView } from './FieldContstraints'; import { FieldDetail } from './FieldDetail'; @@ -24,30 +21,19 @@ import { Badge } from '../../common-elements/'; import { l } from '../../services/Labels'; import { OptionsContext } from '../OptionsProvider'; -import { FieldModel } from '../../services/models/Field'; -import styled from '../../styled-components'; - -const MAX_PATTERN_LENGTH = 45; +import { Pattern } from './Pattern'; +import { ArrayItemDetails } from './ArrayItemDetails'; export class FieldDetails extends React.PureComponent { - state = { - patternShown: false, - }; - static contextType = OptionsContext; - togglePattern = () => { - this.setState({ - patternShown: !this.state.patternShown, - }); - }; - render() { const { showExamples, field, renderDiscriminatorSwitch } = this.props; - const { patternShown } = this.state; - const { enumSkipQuotes, hideSchemaTitles, hideSchemaPattern } = this.context; + const { enumSkipQuotes, hideSchemaTitles } = this.context; const { schema, description, example, deprecated, examples } = field; + const { type } = schema; + const isArrayType = type === 'array'; const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate @@ -92,20 +78,7 @@ export class FieldDetails extends React.PureComponent ({schema.title}) } - {schema.pattern && !hideSchemaPattern && ( - <> - - {patternShown || schema.pattern.length < MAX_PATTERN_LENGTH - ? schema.pattern - : `${schema.pattern.substr(0, MAX_PATTERN_LENGTH)}...`} - - {schema.pattern.length > MAX_PATTERN_LENGTH && ( - - {patternShown ? 'Hide pattern' : 'Show pattern'} - - )} - - )} + {schema.isCircular && {l('recursive')} } {deprecated && ( @@ -114,7 +87,7 @@ export class FieldDetails extends React.PureComponent )} - {!renderDiscriminatorSwitch && }{' '} + {!renderDiscriminatorSwitch && }{' '} {renderedExamples} {}
    @@ -125,44 +98,8 @@ export class FieldDetails extends React.PureComponent) || null} + {isArrayType && schema.items && }
    ); } } - -function Examples({ field }: { field: FieldModel }) { - if (!field.examples) { - return null; - } - - return ( - <> - {l('examples')}: - - {Object.values(field.examples).map((example, idx) => { - return ( -
  • - {getSerializedValue(field, example.value)} - {example.summary || example.description} -
  • - ); - })} -
    - - ); -} - -function getSerializedValue(field: FieldModel, example: any) { - if (field.in) { - // decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138 - return decodeURIComponent(serializeParameterValue(field, example)); - } else { - return example; - } -} - - -const ExamplesList = styled.ul` - margin-top: 1em; - padding-left: 0; - list-style-position: inside; -`; diff --git a/src/components/Fields/Pattern.tsx b/src/components/Fields/Pattern.tsx new file mode 100644 index 00000000..8fdeea8b --- /dev/null +++ b/src/components/Fields/Pattern.tsx @@ -0,0 +1,30 @@ +import { PatternLabel, ToggleButton } from '../../common-elements/fields'; +import * as React from 'react'; +import { OptionsContext } from '../OptionsProvider'; +import { useCallback, useContext, useState } from 'react'; +import { SchemaModel } from '../../services'; + +const MAX_PATTERN_LENGTH = 45; + +export function Pattern(props: {schema: SchemaModel}) { + const pattern = props.schema.pattern; + const { hideSchemaPattern } = useContext(OptionsContext); + const [patternShown, usePatternShown] = useState(false); + const togglePattern = useCallback(() => usePatternShown(!patternShown), [patternShown]); + + if (!pattern || hideSchemaPattern) return null; + + return <> + + {patternShown || pattern.length < MAX_PATTERN_LENGTH + ? pattern + : `${pattern.substr(0, MAX_PATTERN_LENGTH)}...`} + + {pattern.length > MAX_PATTERN_LENGTH && ( + + {patternShown ? 'Hide pattern' : 'Show pattern'} + + )} + +} + diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index d0b081f5..6696e478 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -362,6 +362,15 @@ export function serializeParameterValue( } } +export function getSerializedValue(field: FieldModel, example: any) { + if (field.in) { + // decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138 + return decodeURIComponent(serializeParameterValue(field, example)); + } else { + return example; + } +} + export function langFromMime(contentType: string): string { if (contentType.search(/xml/i) !== -1) { return 'xml';