chore: sections/markdown refactor

This commit is contained in:
Roman Hotsiy 2018-08-16 09:56:47 +03:00
parent e0d82f4aa8
commit eae11682b8
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
20 changed files with 306 additions and 276 deletions

View File

@ -1,19 +1,44 @@
import styled, { media } from '../styled-components'; import { SECTION_ATTR } from '../services/MenuStore';
import styled, { media, withProps } from '../styled-components';
export const MiddlePanel = styled.div` export const MiddlePanel = styled.div`
width: calc(100% - ${props => props.theme.rightPanel.width}); width: calc(100% - ${props => props.theme.rightPanel.width});
padding: ${props => props.theme.spacing.unit * 8}px; padding: 0 ${props => props.theme.spacing.unit * 8}px;
${media.lessThan('medium')` ${media.lessThan('medium')`
width: 100%; width: 100%;
`}; `};
`; `;
export const Section = withProps<{ underlined?: boolean }>(
styled.div.attrs({
[SECTION_ATTR]: props => props.id,
} as any),
)`
padding: ${props => props.theme.spacing.unit * 8}px 0;
${props =>
(props.underlined &&
`
position: relative;
&:not(:last-of-type):after {
position: absolute;
bottom: 0;
width: 100%;
display: block;
content: '';
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
}
`) ||
''}
`;
export const RightPanel = styled.div` export const RightPanel = styled.div`
width: ${props => props.theme.rightPanel.width}; width: ${props => props.theme.rightPanel.width};
color: #fafbfc; color: #fafbfc;
background-color: ${props => props.theme.rightPanel.backgroundColor}; background-color: ${props => props.theme.rightPanel.backgroundColor};
padding: ${props => props.theme.spacing.unit * 8}px; padding: 0 ${props => props.theme.spacing.unit * 8}px;
${media.lessThan('medium')` ${media.lessThan('medium')`
width: 100%; width: 100%;
@ -27,6 +52,7 @@ export const DarkRightPanel = RightPanel.extend`
export const Row = styled.div` export const Row = styled.div`
display: flex; display: flex;
width: 100%; width: 100%;
padding: 0;
${media.lessThan('medium')` ${media.lessThan('medium')`
flex-direction: column; flex-direction: column;

View File

@ -2,6 +2,8 @@ import * as React from 'react';
import PerfectScrollbarType, * as PerfectScrollbarNamespace from 'perfect-scrollbar'; import PerfectScrollbarType, * as PerfectScrollbarNamespace from 'perfect-scrollbar';
import psStyles from 'perfect-scrollbar/css/perfect-scrollbar.css'; import psStyles from 'perfect-scrollbar/css/perfect-scrollbar.css';
import { OptionsContext } from '../components/OptionsProvider';
import styled, { injectGlobal } from '../styled-components'; import styled, { injectGlobal } from '../styled-components';
/* /*
@ -18,11 +20,13 @@ const StyledScrollWrapper = styled.div`
position: relative; position: relative;
`; `;
export class PerfectScrollbar extends React.Component<{ export interface PerfectScrollbarProps {
options?: PerfectScrollbarType.Options; options?: PerfectScrollbarType.Options;
className?: string; className?: string;
updateFn: (fn) => void; updateFn?: (fn) => void;
}> { }
export class PerfectScrollbar extends React.Component<PerfectScrollbarProps> {
private _container: HTMLElement; private _container: HTMLElement;
private inst: PerfectScrollbarType; private inst: PerfectScrollbarType;
@ -49,7 +53,9 @@ export class PerfectScrollbar extends React.Component<{
render() { render() {
const { children, className, updateFn } = this.props; const { children, className, updateFn } = this.props;
updateFn(this.componentDidUpdate.bind(this)); if (updateFn) {
updateFn(this.componentDidUpdate.bind(this));
}
return ( return (
<StyledScrollWrapper className={`scrollbar-container ${className}`} innerRef={this.handleRef}> <StyledScrollWrapper className={`scrollbar-container ${className}`} innerRef={this.handleRef}>
@ -58,3 +64,26 @@ export class PerfectScrollbar extends React.Component<{
); );
} }
} }
export function PerfectScrollbarWrap(
props: PerfectScrollbarProps & { children: JSX.Element[] | JSX.Element },
) {
return (
<OptionsContext.Consumer>
{options =>
!options.nativeScrollbars ? (
<PerfectScrollbar {...props}>{props.children}</PerfectScrollbar>
) : (
<div
style={{
overflow: 'auto',
msOverflowStyle: '-ms-autohiding-scrollbar',
}}
>
{props.children}
</div>
)
}
</OptionsContext.Consumer>
);
}

View File

@ -1,22 +0,0 @@
import * as React from 'react';
import { MiddlePanel, Row } from '../../common-elements/';
import { Markdown } from '../Markdown/Markdown';
export interface ApiDescriptionProps {
description: string;
}
export class ApiDescription extends React.PureComponent<ApiDescriptionProps> {
render() {
const { description } = this.props;
return (
<Row>
<MiddlePanel>
<Markdown source={description} />
</MiddlePanel>
</Row>
);
}
}

View File

@ -3,8 +3,8 @@ import * as React from 'react';
import { AppStore } from '../../services/AppStore'; import { AppStore } from '../../services/AppStore';
import { MiddlePanel, Row } from '../../common-elements/'; import { MiddlePanel, Row, Section } from '../../common-elements/';
import { Markdown } from '../Markdown/Markdown';
import { StyledMarkdownBlock } from '../Markdown/styled.elements'; import { StyledMarkdownBlock } from '../Markdown/styled.elements';
import { import {
ApiHeader, ApiHeader,
@ -70,43 +70,46 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
null; null;
return ( return (
<Row> <Section>
<MiddlePanel className="api-info"> <Row>
<ApiHeader> <MiddlePanel className="api-info">
{info.title} <span>({info.version})</span> <ApiHeader>
</ApiHeader> {info.title} <span>({info.version})</span>
{!hideDownloadButton && ( </ApiHeader>
<p> {!hideDownloadButton && (
Download OpenAPI specification:
<DownloadButton
download={downloadFilename}
target="_blank"
href={downloadLink}
onClick={this.handleDownloadClick}
>
Download
</DownloadButton>
</p>
)}
<StyledMarkdownBlock>
{((info.license || info.contact || info.termsOfService) && (
<InfoSpanBoxWrap>
<InfoSpanBox>
{email} {website} {license} {terms}
</InfoSpanBox>
</InfoSpanBoxWrap>
)) ||
null}
{(externalDocs && (
<p> <p>
<a href={externalDocs.url}>{externalDocs.description || externalDocs.url}</a> Download OpenAPI specification:
<DownloadButton
download={downloadFilename}
target="_blank"
href={downloadLink}
onClick={this.handleDownloadClick}
>
Download
</DownloadButton>
</p> </p>
)) || )}
null} <StyledMarkdownBlock>
</StyledMarkdownBlock> {((info.license || info.contact || info.termsOfService) && (
</MiddlePanel> <InfoSpanBoxWrap>
</Row> <InfoSpanBox>
{email} {website} {license} {terms}
</InfoSpanBox>
</InfoSpanBoxWrap>
)) ||
null}
{(externalDocs && (
<p>
<a href={externalDocs.url}>{externalDocs.description || externalDocs.url}</a>
</p>
)) ||
null}
</StyledMarkdownBlock>
<Markdown source={store.spec.info.description} />
</MiddlePanel>
</Row>
</Section>
); );
} }
} }

View File

@ -1,2 +1 @@
export { ApiDescription } from './ApiDescription';
export { ApiInfo } from './ApiInfo'; export { ApiInfo } from './ApiInfo';

View File

@ -1,10 +1,9 @@
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react'; import * as React from 'react';
import { SECTION_ATTR } from '../../services/MenuStore'; import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
import { Markdown } from '../Markdown/Markdown';
import { H1, H2, MiddlePanel, Row, ShareLink } from '../../common-elements'; import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
import { MDXComponentMeta } from '../../services/MarkdownRenderer'; import { MDXComponentMeta } from '../../services/MarkdownRenderer';
import { ContentItemModel } from '../../services/MenuBuilder'; import { ContentItemModel } from '../../services/MenuBuilder';
import { GroupModel, OperationModel } from '../../services/models'; import { GroupModel, OperationModel } from '../../services/models';
@ -12,20 +11,22 @@ import { Operation } from '../Operation/Operation';
import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes'; import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes';
import { StoreConsumer } from '../StoreBuilder'; import { StoreConsumer } from '../StoreBuilder';
const DEFAULT_ALLOWED_COMPONENTS = {
'security-definitions': {
component: SecurityDefs,
propsSelector: _store => ({
securitySchemes: _store!.spec.securitySchemes,
}),
},
};
@observer @observer
export class ContentItems extends React.Component<{ export class ContentItems extends React.Component<{
items: ContentItemModel[]; items: ContentItemModel[];
allowedMdComponents?: Dict<MDXComponentMeta>; allowedMdComponents?: Dict<MDXComponentMeta>;
}> { }> {
static defaultProps = { static defaultProps = {
allowedMdComponents: { allowedMdComponents: DEFAULT_ALLOWED_COMPONENTS,
'security-definitions': {
component: SecurityDefs,
propsSelector: _store => ({
securitySchemes: _store!.spec.securitySchemes,
}),
},
},
}; };
render() { render() {
@ -34,14 +35,18 @@ export class ContentItems extends React.Component<{
return null; return null;
} }
return items.map(item => ( return items.map(item => (
<ContentItem item={item} key={item.id} allowedMdComponents={this.props.allowedMdComponents} /> <ContentItem
item={item}
key={item.id}
allowedMdComponents={this.props.allowedMdComponents!}
/>
)); ));
} }
} }
export interface ContentItemProps { export interface ContentItemProps {
item: ContentItemModel; item: ContentItemModel;
allowedMdComponents?: Dict<MDXComponentMeta>; allowedMdComponents: Dict<MDXComponentMeta>;
} }
@observer @observer
@ -67,15 +72,21 @@ export class ContentItem extends React.Component<ContentItemProps> {
throw new Error('Unknown item type'); throw new Error('Unknown item type');
} }
return [ return (
<div key="section" {...{ [SECTION_ATTR]: item.id }}> <>
{content} <Section id={item.id} underlined={item.type === 'section'}>
</div>, {content}
(item as any).items && <ContentItems key="content" items={(item as any).items} />, </Section>
]; {item.items && (
<ContentItems items={item.items} allowedMdComponents={this.props.allowedMdComponents} />
)}
</>
);
} }
} }
const middlePanelWrap = component => <MiddlePanel>{component}</MiddlePanel>;
@observer @observer
export class SectionItem extends React.Component<ContentItemProps> { export class SectionItem extends React.Component<ContentItemProps> {
render() { render() {
@ -83,23 +94,26 @@ export class SectionItem extends React.Component<ContentItemProps> {
const components = this.props.allowedMdComponents; const components = this.props.allowedMdComponents;
const Header = level === 2 ? H2 : H1; const Header = level === 2 ? H2 : H1;
return ( return (
<Row> <>
<MiddlePanel> <Row>
<Header> <MiddlePanel>
<ShareLink href={'#' + this.props.item.id} /> <Header>
{name} <ShareLink href={'#' + this.props.item.id} />
</Header> {name}
{components ? ( </Header>
<StoreConsumer> </MiddlePanel>
{store => ( </Row>
<Markdown source={description || ''} allowedComponents={components} store={store} /> <StoreConsumer>
)} {store => (
</StoreConsumer> <AdvancedMarkdown
) : ( source={description || ''}
<Markdown source={description || ''} /> allowedComponents={components}
store={store}
htmlWrap={middlePanelWrap}
/>
)} )}
</MiddlePanel> </StoreConsumer>
</Row> </>
); );
} }
} }

View File

@ -0,0 +1,38 @@
import * as React from 'react';
import { AppStore, MarkdownRenderer, MDXComponentMeta } from '../../services';
import { BaseMarkdownProps } from './Markdown';
import { SanitizedMarkdownHTML } from './SanitizedMdBlock';
export interface AdvancedMarkdownProps extends BaseMarkdownProps {
store?: AppStore;
allowedComponents: Dict<MDXComponentMeta>;
htmlWrap?: (part: JSX.Element) => JSX.Element;
}
export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
render() {
const { store, source, allowedComponents, htmlWrap = i => i } = this.props;
if (!store) {
throw new Error('When using componentes in markdown, store prop must be provided');
}
const renderer = new MarkdownRenderer();
const parts = renderer.renderMdWithComponents(source, allowedComponents);
if (!parts.length) {
return null;
}
return parts.map((part, idx) => {
if (typeof part === 'string') {
return React.cloneElement(
htmlWrap(<SanitizedMarkdownHTML html={part} inline={false} dense={false} />),
{ key: idx },
);
}
return <part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} />;
});
}
}

View File

@ -1,103 +1,35 @@
import * as React from 'react'; import * as React from 'react';
import * as DOMPurify from 'dompurify'; import { MarkdownRenderer } from '../../services';
import { AppStore, MarkdownRenderer, MDXComponentMeta } from '../../services'; import { SanitizedMarkdownHTML } from './SanitizedMdBlock';
import { OptionsContext } from '../OptionsProvider';
import { StyledMarkdownBlock } from './styled.elements';
const StyledMarkdownSpan = StyledMarkdownBlock.withComponent('span');
export interface StylingMarkdownProps { export interface StylingMarkdownProps {
dense?: boolean; dense?: boolean;
inline?: boolean; inline?: boolean;
} }
export interface BaseMarkdownProps extends StylingMarkdownProps { export interface BaseMarkdownProps {
sanitize?: boolean; sanitize?: boolean;
store?: AppStore;
}
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html);
function SanitizedMarkdownHTML(props: StylingMarkdownProps & { html: string }) {
const Wrap = props.inline ? StyledMarkdownSpan : StyledMarkdownBlock;
return (
<OptionsContext.Consumer>
{options => (
<Wrap
className={'redoc-markdown'}
dangerouslySetInnerHTML={{
__html: sanitize(options.untrustedSpec, props.html),
}}
{...props}
/>
)}
</OptionsContext.Consumer>
);
}
export interface MarkdownProps extends BaseMarkdownProps {
allowedComponents?: Dict<MDXComponentMeta>;
source: string; source: string;
} }
export type MarkdownProps = BaseMarkdownProps &
StylingMarkdownProps & {
source: string;
className?: string;
};
export class Markdown extends React.Component<MarkdownProps> { export class Markdown extends React.Component<MarkdownProps> {
constructor(props: MarkdownProps) {
super(props);
if (props.allowedComponents && props.inline) {
throw new Error('Markdown Component: "inline" mode doesn\'t support "components"');
}
}
render() { render() {
const { source, allowedComponents, store, inline, dense } = this.props; const { source, inline, dense, className } = this.props;
if (allowedComponents && !store) {
throw new Error('When using componentes in markdown, store prop must be provided');
}
const renderer = new MarkdownRenderer(); const renderer = new MarkdownRenderer();
if (allowedComponents) {
return (
<AdvancedMarkdown
parts={renderer.renderMdWithComponents(source, allowedComponents)}
{...this.props}
/>
);
} else {
return (
<SanitizedMarkdownHTML html={renderer.renderMd(source)} inline={inline} dense={dense} />
);
}
}
}
export interface AdvancedMarkdownProps extends BaseMarkdownProps {
parts: Array<string | MDXComponentMeta>;
}
export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
render() {
const { inline, dense, store, parts } = this.props;
if (!parts.length) {
return null;
}
return ( return (
<> <SanitizedMarkdownHTML
{parts.map( html={renderer.renderMd(source)}
(part, idx) => inline={inline}
typeof part === 'string' ? ( dense={dense}
<SanitizedMarkdownHTML html={part} inline={inline} dense={dense} key={idx} /> className={className}
) : ( />
<part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} />
),
)}
</>
); );
} }
} }

View File

@ -0,0 +1,30 @@
import * as DOMPurify from 'dompurify';
import * as React from 'react';
import { OptionsContext } from '../OptionsProvider';
import { StylingMarkdownProps } from './Markdown';
import { StyledMarkdownBlock } from './styled.elements';
const StyledMarkdownSpan = StyledMarkdownBlock.withComponent('span');
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html);
export function SanitizedMarkdownHTML(
props: StylingMarkdownProps & { html: string; className?: string },
) {
const Wrap = props.inline ? StyledMarkdownSpan : StyledMarkdownBlock;
return (
<OptionsContext.Consumer>
{options => (
<Wrap
className={'redoc-markdown ' + (props.className || '')}
dangerouslySetInnerHTML={{
__html: sanitize(options.untrustedSpec, props.html),
}}
{...props}
/>
)}
</OptionsContext.Consumer>
);
}

View File

@ -23,20 +23,10 @@ const OperationRow = Row.extend`
contain: content; contain: content;
overflow: hidden; overflow: hidden;
position: relative;
&:after {
position: absolute;
bottom: 0;
width: 100%;
display: block;
content: '';
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
}
`; `;
const Description = styled(Markdown)` const Description = styled(Markdown)`
margin-bottom: ${({ theme }) => theme.spacing.unit * 8}; margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px;
`; `;
export interface OperationProps { export interface OperationProps {

View File

@ -4,7 +4,7 @@ import * as React from 'react';
import { ThemeProvider } from '../../styled-components'; import { ThemeProvider } from '../../styled-components';
import { AppStore } from '../../services'; import { AppStore } from '../../services';
import { ApiDescription, ApiInfo } from '../ApiInfo/'; import { ApiInfo } from '../ApiInfo/';
import { ApiLogo } from '../ApiLogo/ApiLogo'; import { ApiLogo } from '../ApiLogo/ApiLogo';
import { ContentItems } from '../ContentItems/ContentItems'; import { ContentItems } from '../ContentItems/ContentItems';
import { OptionsProvider } from '../OptionsProvider'; import { OptionsProvider } from '../OptionsProvider';
@ -57,7 +57,6 @@ export class Redoc extends React.Component<RedocProps> {
</StickyResponsiveSidebar> </StickyResponsiveSidebar>
<ApiContentWrap className="api-content"> <ApiContentWrap className="api-content">
<ApiInfo store={store} /> <ApiInfo store={store} />
<ApiDescription description={store.spec.info.description} />
<ContentItems items={menu.items as any} /> <ContentItems items={menu.items as any} />
</ApiContentWrap> </ApiContentWrap>
<BackgroundStub /> <BackgroundStub />

View File

@ -7,6 +7,7 @@ import { MenuItem } from '../SideMenu/MenuItem';
import { MarkerService } from '../../services/MarkerService'; import { MarkerService } from '../../services/MarkerService';
import { SearchResult } from '../../services/SearchWorker.worker'; import { SearchResult } from '../../services/SearchWorker.worker';
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
import { import {
ClearIcon, ClearIcon,
SearchIcon, SearchIcon,
@ -135,21 +136,27 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
onChange={this.search} onChange={this.search}
/> />
{results.length > 0 && ( {results.length > 0 && (
<SearchResultsBox data-role="search:results"> <PerfectScrollbarWrap
{results.map((res, idx) => ( options={{
<MenuItem wheelPropagation: false,
item={Object.create(res.item, { }}
active: { >
value: idx === activeItemIdx, <SearchResultsBox data-role="search:results">
}, {results.map((res, idx) => (
})} <MenuItem
onActivate={this.props.onActivate} item={Object.create(res.item, {
withoutChildren={true} active: {
key={res.item.id} value: idx === activeItemIdx,
data-role="search:result" },
/> })}
))} onActivate={this.props.onActivate}
</SearchResultsBox> withoutChildren={true}
key={res.item.id}
data-role="search:result"
/>
))}
</SearchResultsBox>
</PerfectScrollbarWrap>
)} )}
</SearchWrap> </SearchWrap>
); );

View File

@ -58,7 +58,6 @@ export const SearchResultsBox = styled.div`
margin-top: 10px; margin-top: 10px;
line-height: 1.4; line-height: 1.4;
font-size: 0.9em; font-size: 0.9em;
overflow: auto;
${MenuItemLabel} { ${MenuItemLabel} {
padding-top: 6px; padding-top: 6px;

View File

@ -82,20 +82,23 @@ export class SecurityRequirement extends React.PureComponent<SecurityRequirement
} }
} }
const AuthHeaderColumn = styled.td``; const AuthHeaderColumn = styled.div`
flex: 1;
`;
const SecuritiesColumn = styled.td` const SecuritiesColumn = styled.div`
width: ${props => props.theme.schema.defaultDetailsWidth}; width: ${props => props.theme.schema.defaultDetailsWidth};
`; `;
const AuthHeader = UnderlinedHeader.extend` const AuthHeader = UnderlinedHeader.extend`
display: inline-block; display: inline-block;
margin: 0;
`; `;
const Table = styled.table` const Wrap = styled.div`
width: 100%; width: 100%;
border-collapse: collapse; display: flex;
font-size: inherit; margin: 1em 0;
`; `;
export interface SecurityRequirementsProps { export interface SecurityRequirementsProps {
@ -109,20 +112,14 @@ export class SecurityRequirements extends React.PureComponent<SecurityRequiremen
return null; return null;
} }
return ( return (
<Table> <Wrap>
<tbody> <AuthHeaderColumn>
<tr> <AuthHeader>Authorizations: </AuthHeader>
<AuthHeaderColumn> </AuthHeaderColumn>
<AuthHeader>Authorizations: </AuthHeader> <SecuritiesColumn>
</AuthHeaderColumn> {securities.map((security, idx) => <SecurityRequirement key={idx} security={security} />)}
<SecuritiesColumn> </SecuritiesColumn>
{securities.map((security, idx) => ( </Wrap>
<SecurityRequirement key={idx} security={security} />
))}
</SecuritiesColumn>
</tr>
</tbody>
</Table>
); );
} }
} }

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import { SecuritySchemesModel } from '../../services/models'; import { SecuritySchemesModel } from '../../services/models';
import { H2, ShareLink } from '../../common-elements'; import { H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
import { OpenAPISecurityScheme } from '../../types'; import { OpenAPISecurityScheme } from '../../types';
import { Markdown } from '../Markdown/Markdown'; import { Markdown } from '../Markdown/Markdown';
import { StyledMarkdownBlock } from '../Markdown/styled.elements'; import { StyledMarkdownBlock } from '../Markdown/styled.elements';
@ -66,10 +66,10 @@ export interface SecurityDefsProps {
export class SecurityDefs extends React.PureComponent<SecurityDefsProps> { export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
render() { render() {
return ( return this.props.securitySchemes.schemes.map(scheme => (
<div> <Section id={scheme.sectionId} key={scheme.id}>
{this.props.securitySchemes.schemes.map(scheme => ( <Row>
<div data-section-id={scheme.sectionId} key={scheme.id}> <MiddlePanel>
<H2> <H2>
<ShareLink href={'#' + scheme.sectionId} /> <ShareLink href={'#' + scheme.sectionId} />
{scheme.id} {scheme.id}
@ -118,9 +118,9 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
</tbody> </tbody>
</table> </table>
</StyledMarkdownBlock> </StyledMarkdownBlock>
</div> </MiddlePanel>
))} </Row>
</div> </Section>
); ));
} }
} }

View File

@ -1,11 +1,10 @@
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react'; import * as React from 'react';
import { OptionsContext } from '../OptionsProvider';
import { IMenuItem, MenuStore } from '../../services/MenuStore'; import { IMenuItem, MenuStore } from '../../services/MenuStore';
import { MenuItems } from './MenuItems'; import { MenuItems } from './MenuItems';
import { PerfectScrollbar } from '../../common-elements/perfect-scrollbar'; import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
import { RedocAttribution } from './styled.elements'; import { RedocAttribution } from './styled.elements';
@observer @observer
@ -15,31 +14,20 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
render() { render() {
const store = this.props.menu; const store = this.props.menu;
return ( return (
<OptionsContext.Consumer> <PerfectScrollbarWrap
{options => updateFn={this.saveScrollUpdate}
options.nativeScrollbars ? ( className={this.props.className}
<MenuItems options={{
className={this.props.className} wheelPropagation: false,
style={{ }}
overflow: 'auto', >
msOverflowStyle: '-ms-autohiding-scrollbar', <MenuItems items={store.items} onActivate={this.activate} root={true} />
}} <RedocAttribution>
items={store.items} <a target="_blank" href="https://github.com/Rebilly/ReDoc">
onActivate={this.activate} Documentation Powered by ReDoc
root={true} </a>
/> </RedocAttribution>
) : ( </PerfectScrollbarWrap>
<PerfectScrollbar updateFn={this.saveScrollUpdate} className={this.props.className}>
<MenuItems items={store.items} onActivate={this.activate} root={true} />
<RedocAttribution>
<a target="_blank" href="https://github.com/Rebilly/ReDoc">
Documentation Powered by ReDoc
</a>
</RedocAttribution>
</PerfectScrollbar>
)
}
</OptionsContext.Consumer>
); );
} }

View File

@ -164,9 +164,10 @@ export const MenuItemTitle = withProps<{ width?: string }>(styled.span)`
`; `;
export const RedocAttribution = styled.div` export const RedocAttribution = styled.div`
${({ theme }) => `
font-size: 0.8em; font-size: 0.8em;
margin-top: ${({ theme }) => `${theme.spacing.unit * 2}px`}; margin-top: ${theme.spacing.unit * 2}px;
padding: ${({ theme }) => `0 ${theme.spacing.unit * 4}px`}; padding: 0 ${theme.spacing.unit * 4}px;
text-align: left; text-align: left;
opacity: 0.7; opacity: 0.7;
@ -174,9 +175,10 @@ export const RedocAttribution = styled.div`
a, a,
a:visited, a:visited,
a:hover { a:hover {
color: ${({ theme }) => theme.colors.text.primary} !important; color: ${theme.colors.text.primary} !important;
border-top: 1px solid #e1e1e1; border-top: 1px solid #e1e1e1;
padding-top: 10px; padding: ${theme.spacing.unit}px 0;
display: block; display: block;
} }
`};
`; `;

View File

@ -1,7 +1,6 @@
export * from './RedocStandalone'; export * from './RedocStandalone';
export * from './Redoc/Redoc'; export * from './Redoc/Redoc';
export * from './ApiInfo/ApiInfo'; export * from './ApiInfo/ApiInfo';
export * from './ApiInfo/ApiDescription';
export * from './ApiLogo/ApiLogo'; export * from './ApiLogo/ApiLogo';
export * from './ContentItems/ContentItems'; export * from './ContentItems/ContentItems';
export { ApiContentWrap, BackgroundStub, RedocWrap } from './Redoc/styled.elements'; export { ApiContentWrap, BackgroundStub, RedocWrap } from './Redoc/styled.elements';

View File

@ -1,5 +1,5 @@
export * from './components'; export * from './components';
export { MiddlePanel, Row, RightPanel } from './common-elements/'; export { MiddlePanel, Row, RightPanel, Section } from './common-elements/';
export * from './services'; export * from './services';
export * from './utils'; export * from './utils';