mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-08 14:14:56 +03:00
Match rendering of callbacks to style of operation responses
This commit is contained in:
parent
01809ca644
commit
4bd2e70940
87
src/components/CallbackSamples/CallbackSamples.tsx
Normal file
87
src/components/CallbackSamples/CallbackSamples.tsx
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { isPayloadSample, RedocNormalizedOptions } from '../../services';
|
||||||
|
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
|
||||||
|
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
|
||||||
|
|
||||||
|
import { RightPanelHeader, Tab, TabList, TabPanel, Tabs } from '../../common-elements';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
import { CallbackModel } from '../../services/models';
|
||||||
|
import { Endpoint } from '../Endpoint/Endpoint';
|
||||||
|
|
||||||
|
export interface CallbackSamplesProps {
|
||||||
|
callbacks: CallbackModel[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class CallbackSamples extends React.Component<CallbackSamplesProps> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
|
context: RedocNormalizedOptions;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { callbacks } = this.props;
|
||||||
|
|
||||||
|
// Sums number of code samples per operation per callback
|
||||||
|
const numSamples = callbacks.reduce(
|
||||||
|
(callbackSum, callback) =>
|
||||||
|
callbackSum +
|
||||||
|
callback.operations.reduce(
|
||||||
|
(sampleSum, operation) => sampleSum + operation.codeSamples.length,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasSamples = numSamples > 0;
|
||||||
|
const hideTabList = numSamples === 1 ? this.context.hideSingleRequestSampleTab : false;
|
||||||
|
|
||||||
|
const renderTabs = () => {
|
||||||
|
return callbacks.map(callback => {
|
||||||
|
return callback.operations.map(operation => {
|
||||||
|
return operation.codeSamples.map(sample => {
|
||||||
|
return (
|
||||||
|
<Tab key={operation.id + '_' + operation.name}>
|
||||||
|
{operation.name} {sample.label !== undefined ? sample.label : sample.lang}
|
||||||
|
</Tab>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTabPanels = () => {
|
||||||
|
return callbacks.map(callback => {
|
||||||
|
return callback.operations.map(operation => {
|
||||||
|
return operation.codeSamples.map(sample => {
|
||||||
|
return (
|
||||||
|
<TabPanel key={sample.lang + '_' + (sample.label || '')}>
|
||||||
|
{isPayloadSample(sample) ? (
|
||||||
|
<div>
|
||||||
|
<Endpoint operation={operation} inverted={false} />
|
||||||
|
<PayloadSamples content={sample.requestBodyContent} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<SourceCodeWithCopy lang={sample.lang} source={sample.source} />
|
||||||
|
)}
|
||||||
|
</TabPanel>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
(hasSamples && (
|
||||||
|
<div>
|
||||||
|
<RightPanelHeader> Callback samples </RightPanelHeader>
|
||||||
|
|
||||||
|
<Tabs defaultIndex={0}>
|
||||||
|
<TabList hidden={hideTabList}>{renderTabs()}</TabList>
|
||||||
|
{renderTabPanels()}
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
)) ||
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,29 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { CallbackModel } from '../../services/models';
|
import { OperationModel } from '../../services/models';
|
||||||
|
import { CallbackDetails } from './CallbackDetails';
|
||||||
import { CallbackDetailsWrap, StyledCallbackTitle } from '../Callbacks/styled.elements';
|
import { CallbackDetailsWrap, StyledCallbackTitle } from '../Callbacks/styled.elements';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class CallbackView extends React.Component<{ callback: CallbackModel }> {
|
export class CallbackView extends React.Component<{ callbackOperation: OperationModel }> {
|
||||||
toggle = () => {
|
toggle = () => {
|
||||||
this.props.callback.toggle();
|
this.props.callbackOperation.toggle();
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { name, expanded } = this.props.callback;
|
const { name, description, expanded } = this.props.callbackOperation;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<StyledCallbackTitle onClick={this.toggle} name={name} opened={expanded} />
|
<StyledCallbackTitle
|
||||||
|
onClick={this.toggle}
|
||||||
|
name={name}
|
||||||
|
description={description}
|
||||||
|
opened={expanded}
|
||||||
|
/>
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<CallbackDetailsWrap>
|
<CallbackDetailsWrap>
|
||||||
<span>{name}</span>
|
<CallbackDetails callbackOperation={this.props.callbackOperation} />
|
||||||
</CallbackDetailsWrap>
|
</CallbackDetailsWrap>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
9
src/components/Callbacks/CallbackDetails.tsx
Normal file
9
src/components/Callbacks/CallbackDetails.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { OperationModel } from '../../services/models';
|
||||||
|
import { OperationItem } from '../ContentItems/ContentItems';
|
||||||
|
|
||||||
|
export class CallbackDetails extends React.PureComponent<{ callbackOperation: OperationModel }> {
|
||||||
|
render() {
|
||||||
|
return <OperationItem item={this.props.callbackOperation} />;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { ShelfIcon } from '../../common-elements';
|
import { ShelfIcon } from '../../common-elements';
|
||||||
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
|
|
||||||
export interface CallbackTitleProps {
|
export interface CallbackTitleProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
description?: string;
|
||||||
opened?: boolean;
|
opened?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
@ -11,11 +13,12 @@ export interface CallbackTitleProps {
|
||||||
|
|
||||||
export class CallbackTitle extends React.PureComponent<CallbackTitleProps> {
|
export class CallbackTitle extends React.PureComponent<CallbackTitleProps> {
|
||||||
render() {
|
render() {
|
||||||
const { name, opened, className, onClick } = this.props;
|
const { name, description, opened, className, onClick } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={className} onClick={onClick || undefined}>
|
<div className={className} onClick={onClick || undefined}>
|
||||||
<ShelfIcon size={'1.5em'} direction={opened ? 'up' : 'down'} float={'left'} />
|
<ShelfIcon size={'1.5em'} direction={opened ? 'down' : 'right'} float={'left'} />
|
||||||
<strong>{name} </strong>
|
<strong>{name} </strong>
|
||||||
|
{description && <Markdown compact={true} inline={true} source={description} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,9 @@ export class CallbacksList extends React.PureComponent<CallbacksListProps> {
|
||||||
<div>
|
<div>
|
||||||
<CallbacksHeader> Callbacks </CallbacksHeader>
|
<CallbacksHeader> Callbacks </CallbacksHeader>
|
||||||
{callbacks.map(callback => {
|
{callbacks.map(callback => {
|
||||||
return <CallbackView key={callback.name} callback={callback} />;
|
return callback.operations.map(operation => {
|
||||||
|
return <CallbackView key={callback.name} callbackOperation={operation} />;
|
||||||
|
});
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,15 +8,6 @@ import { Operation } from '..';
|
||||||
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
||||||
import { ContentItemModel } from '../../services';
|
import { ContentItemModel } from '../../services';
|
||||||
import { GroupModel, OperationModel } from '../../services/models';
|
import { GroupModel, OperationModel } from '../../services/models';
|
||||||
import styled from '../../styled-components';
|
|
||||||
|
|
||||||
const CallbacksHeader = styled.h3`
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 0 40px;
|
|
||||||
margin: 3em 0 1.1em;
|
|
||||||
color: #253137;
|
|
||||||
font-weight: normal;
|
|
||||||
`;
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ContentItems extends React.Component<{
|
export class ContentItems extends React.Component<{
|
||||||
|
@ -28,18 +19,6 @@ export class ContentItems extends React.Component<{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return items.map(item => {
|
return items.map(item => {
|
||||||
if (item.type === 'operation' && item.callbacks.length > 0) {
|
|
||||||
return (
|
|
||||||
<React.Fragment key={item.id}>
|
|
||||||
<ContentItem item={item} />
|
|
||||||
<CallbacksHeader>Callbacks</CallbacksHeader>
|
|
||||||
{item.callbacks.map((callbackIndex, idx) => {
|
|
||||||
return <ContentItems key={idx} items={callbackIndex.operations} />;
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ContentItem key={item.id} item={item} />;
|
return <ContentItem key={item.id} item={item} />;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,18 @@ import { Parameters } from '../Parameters/Parameters';
|
||||||
import { RequestSamples } from '../RequestSamples/RequestSamples';
|
import { RequestSamples } from '../RequestSamples/RequestSamples';
|
||||||
import { ResponsesList } from '../Responses/ResponsesList';
|
import { ResponsesList } from '../Responses/ResponsesList';
|
||||||
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
|
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
|
||||||
|
import { CallbacksList } from '../Callbacks';
|
||||||
|
import { CallbackSamples } from '../CallbackSamples/CallbackSamples';
|
||||||
|
|
||||||
import { OperationModel as OperationType } from '../../services/models';
|
import { OperationModel as OperationType } from '../../services/models';
|
||||||
import styled from '../../styled-components';
|
import styled from '../../styled-components';
|
||||||
import { Extensions } from '../Fields/Extensions';
|
import { Extensions } from '../Fields/Extensions';
|
||||||
|
|
||||||
|
const CallbackMiddlePanel = styled(MiddlePanel)`
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
const OperationRow = styled(Row)`
|
const OperationRow = styled(Row)`
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
contain: content;
|
contain: content;
|
||||||
|
@ -42,18 +49,23 @@ export class Operation extends React.Component<OperationProps> {
|
||||||
|
|
||||||
const { name: summary, description, deprecated, externalDocs } = operation;
|
const { name: summary, description, deprecated, externalDocs } = operation;
|
||||||
const hasDescription = !!(description || externalDocs);
|
const hasDescription = !!(description || externalDocs);
|
||||||
|
const AdaptiveMiddlePanel = operation.isCallback ? CallbackMiddlePanel : MiddlePanel;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionsContext.Consumer>
|
<OptionsContext.Consumer>
|
||||||
{options => (
|
{options => (
|
||||||
<OperationRow>
|
<OperationRow>
|
||||||
<MiddlePanel>
|
<AdaptiveMiddlePanel>
|
||||||
<H2>
|
{!operation.isCallback && (
|
||||||
<ShareLink to={operation.id} />
|
<H2>
|
||||||
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
|
<ShareLink to={operation.id} />
|
||||||
</H2>
|
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
|
||||||
{options.pathInMiddlePanel && <Endpoint operation={operation} inverted={true} />}
|
</H2>
|
||||||
{hasDescription && (
|
)}
|
||||||
|
{!operation.isCallback && options.pathInMiddlePanel && (
|
||||||
|
<Endpoint operation={operation} inverted={true} />
|
||||||
|
)}
|
||||||
|
{!operation.isCallback && hasDescription && (
|
||||||
<Description>
|
<Description>
|
||||||
{description !== undefined && <Markdown source={description} />}
|
{description !== undefined && <Markdown source={description} />}
|
||||||
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
|
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
|
||||||
|
@ -63,12 +75,18 @@ export class Operation extends React.Component<OperationProps> {
|
||||||
<SecurityRequirements securities={operation.security} />
|
<SecurityRequirements securities={operation.security} />
|
||||||
<Parameters parameters={operation.parameters} body={operation.requestBody} />
|
<Parameters parameters={operation.parameters} body={operation.requestBody} />
|
||||||
<ResponsesList responses={operation.responses} />
|
<ResponsesList responses={operation.responses} />
|
||||||
</MiddlePanel>
|
<CallbacksList callbacks={operation.callbacks} />
|
||||||
<DarkRightPanel>
|
</AdaptiveMiddlePanel>
|
||||||
{!options.pathInMiddlePanel && <Endpoint operation={operation} />}
|
{!operation.isCallback && (
|
||||||
<RequestSamples operation={operation} />
|
<DarkRightPanel>
|
||||||
<ResponseSamples operation={operation} />
|
{!options.pathInMiddlePanel && <Endpoint operation={operation} />}
|
||||||
</DarkRightPanel>
|
<RequestSamples operation={operation} />
|
||||||
|
<ResponseSamples operation={operation} />
|
||||||
|
{operation.callbacks.length > 0 && (
|
||||||
|
<CallbackSamples callbacks={operation.callbacks} />
|
||||||
|
)}
|
||||||
|
</DarkRightPanel>
|
||||||
|
)}
|
||||||
</OperationRow>
|
</OperationRow>
|
||||||
)}
|
)}
|
||||||
</OptionsContext.Consumer>
|
</OptionsContext.Consumer>
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe('Components', () => {
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
const callbackViewElement = shallow(
|
const callbackViewElement = shallow(
|
||||||
<CallbackView key={callback.name} callback={callback} />,
|
<CallbackView key={callback.name} callbackOperation={callback.operations[0]} />,
|
||||||
).getElement();
|
).getElement();
|
||||||
expect(callbackViewElement.props).toBeDefined();
|
expect(callbackViewElement.props).toBeDefined();
|
||||||
expect(callbackViewElement.props.children).toBeDefined();
|
expect(callbackViewElement.props.children).toBeDefined();
|
||||||
|
|
|
@ -172,6 +172,14 @@ export class OperationModel implements IMenuItem {
|
||||||
this.active = false;
|
this.active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle expansion in middle panel (for callbacks, which are operations)
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
toggle() {
|
||||||
|
this.expanded = !this.expanded;
|
||||||
|
}
|
||||||
|
|
||||||
expand() {
|
expand() {
|
||||||
if (this.parent) {
|
if (this.parent) {
|
||||||
this.parent.expand();
|
this.parent.expand();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user