mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-08 14:14:56 +03:00
beta version
This commit is contained in:
parent
12cfb6ebde
commit
aa80c7d6e0
|
@ -22,7 +22,7 @@ const swagger = window.location.search.indexOf('swagger') > -1; // compatibility
|
||||||
const specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml';
|
const specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml';
|
||||||
|
|
||||||
let store;
|
let store;
|
||||||
const options: RedocRawOptions = { nativeScrollbars: false };
|
const options: RedocRawOptions = { nativeScrollbars: false, parentElementSelector: '#redoc-container' };
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const spec = await loadAndBundleSpec(specUrl);
|
const spec = await loadAndBundleSpec(specUrl);
|
||||||
|
|
|
@ -41,7 +41,7 @@ export class Redoc extends React.Component<RedocProps> {
|
||||||
<ThemeProvider theme={options.theme}>
|
<ThemeProvider theme={options.theme}>
|
||||||
<StoreProvider value={this.props.store}>
|
<StoreProvider value={this.props.store}>
|
||||||
<OptionsProvider value={options}>
|
<OptionsProvider value={options}>
|
||||||
<RedocWrap className="redoc-wrap">
|
<RedocWrap ref={menu.scroll.setRedocElement} className="redoc-wrap">
|
||||||
<StickyResponsiveSidebar menu={menu} className="menu-content">
|
<StickyResponsiveSidebar menu={menu} className="menu-content">
|
||||||
<ApiLogo info={spec.info} />
|
<ApiLogo info={spec.info} />
|
||||||
{(!options.disableSearch && (
|
{(!options.disableSearch && (
|
||||||
|
@ -58,8 +58,8 @@ export class Redoc extends React.Component<RedocProps> {
|
||||||
<ApiContentWrap className="api-content">
|
<ApiContentWrap className="api-content">
|
||||||
<ApiInfo store={store} />
|
<ApiInfo store={store} />
|
||||||
<ContentItems items={menu.items as any} />
|
<ContentItems items={menu.items as any} />
|
||||||
|
<BackgroundStub />
|
||||||
</ApiContentWrap>
|
</ApiContentWrap>
|
||||||
<BackgroundStub />
|
|
||||||
</RedocWrap>
|
</RedocWrap>
|
||||||
</OptionsProvider>
|
</OptionsProvider>
|
||||||
</StoreProvider>
|
</StoreProvider>
|
||||||
|
|
|
@ -8,7 +8,6 @@ export const RedocWrap = styled.div`
|
||||||
line-height: ${theme.typography.lineHeight};
|
line-height: ${theme.typography.lineHeight};
|
||||||
color: ${theme.colors.text.primary};
|
color: ${theme.colors.text.primary};
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
-webkit-font-smoothing: ${theme.typography.smoothing};
|
-webkit-font-smoothing: ${theme.typography.smoothing};
|
||||||
|
@ -43,10 +42,11 @@ export const BackgroundStub = styled.div`
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
z-index: -1;
|
||||||
width: ${({ theme }) => {
|
width: ${({ theme }) => {
|
||||||
if (theme.rightPanel.width.endsWith('%')) {
|
if (theme.rightPanel.width.endsWith('%')) {
|
||||||
const percents = parseInt(theme.rightPanel.width, 10);
|
const percents = parseInt(theme.rightPanel.width, 10);
|
||||||
return `calc((100% - ${theme.menu.width}) * ${percents / 100})`;
|
return `calc(100% * ${percents / 100})`;
|
||||||
} else {
|
} else {
|
||||||
return theme.rightPanel.width;
|
return theme.rightPanel.width;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
import { MenuStore } from '../../services/MenuStore';
|
import { MenuStore } from '../../services/MenuStore';
|
||||||
import { RedocNormalizedOptions, RedocRawOptions } from '../../services/RedocNormalizedOptions';
|
import { RedocNormalizedOptions, RedocRawOptions } from '../../services/RedocNormalizedOptions';
|
||||||
|
@ -66,9 +67,10 @@ const FloatingButton = styled.div`
|
||||||
`};
|
`};
|
||||||
|
|
||||||
bottom: 44px;
|
bottom: 44px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
height: 60px;
|
||||||
|
width: 20px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
|
@ -76,11 +78,101 @@ const FloatingButton = styled.div`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
export interface FloatingButtonProps {
|
||||||
|
onClick?: () => void;
|
||||||
|
parentElement: Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class PortaledFloatingButton extends React.Component<FloatingButtonProps> {
|
||||||
|
portalRoot: HTMLElement;
|
||||||
|
floatingButtonRef: HTMLElement;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.portalRoot = document.createElement('div');
|
||||||
|
this.portalRoot.classList.add('portalled-element');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private setFloatingButtonRef = (node) => {
|
||||||
|
this.floatingButtonRef = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.body.removeChild(this.portalRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { parentElement } = this.props;
|
||||||
|
|
||||||
|
document.body.appendChild(this.portalRoot);
|
||||||
|
this.floatingButtonRef.style.top = `${parentElement.getBoundingClientRect().bottom - 88}px`;
|
||||||
|
this.floatingButtonRef.style.left = `${parentElement.getBoundingClientRect().right - 88}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return createPortal(
|
||||||
|
<FloatingButton ref={this.setFloatingButtonRef} {...this.props} />,
|
||||||
|
this.portalRoot,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class PortaledStickySidebar extends React.Component<FloatingButtonProps> {
|
||||||
|
portalRoot: HTMLElement;
|
||||||
|
stickySidebarRef: HTMLElement;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.portalRoot = document.createElement('div');
|
||||||
|
this.portalRoot.classList.add('portalled-element');
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.body.removeChild(this.portalRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setStickySidebarRef = (node) => {
|
||||||
|
this.stickySidebarRef = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { parentElement } = this.props;
|
||||||
|
|
||||||
|
document.body.appendChild(this.portalRoot);
|
||||||
|
this.stickySidebarRef.style.top = `${parentElement.getBoundingClientRect().top}px`;
|
||||||
|
this.stickySidebarRef.style.left = `${parentElement.getBoundingClientRect().left}px`;
|
||||||
|
this.stickySidebarRef.style.width = `${parentElement.clientWidth}px`;
|
||||||
|
this.stickySidebarRef.style.zIndex = '99';
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return createPortal(
|
||||||
|
<StyledStickySidebar ref={this.setStickySidebarRef} {...this.props} />,
|
||||||
|
this.portalRoot,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class StickyResponsiveSidebar extends React.Component<StickySidebarProps> {
|
export class StickyResponsiveSidebar extends React.Component<StickySidebarProps> {
|
||||||
stickyElement: Element;
|
stickyElement: Element;
|
||||||
|
portalRoot: Element;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.portalRoot = document.createElement('div');
|
||||||
|
this.portalRoot.classList.add('portalled-element');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
document.body.appendChild(this.portalRoot);
|
||||||
|
|
||||||
if (stickyfill) {
|
if (stickyfill) {
|
||||||
stickyfill.add(this.stickyElement);
|
stickyfill.add(this.stickyElement);
|
||||||
}
|
}
|
||||||
|
@ -102,37 +194,52 @@ export class StickyResponsiveSidebar extends React.Component<StickySidebarProps>
|
||||||
return top + 'px';
|
return top + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
getSidebarStyles = options => {
|
||||||
const open = this.props.menu.sideBarOpened;
|
if (options.parentElement instanceof Element) {
|
||||||
|
return {
|
||||||
const style = options => {
|
height: options.parentElement.offsetHeight,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
const top = this.getScrollYOffset(options);
|
const top = this.getScrollYOffset(options);
|
||||||
return {
|
return {
|
||||||
top,
|
top,
|
||||||
height: `calc(100vh - ${top})`,
|
height: `calc(100vh - ${top})`,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setStickyElementRef = el => {
|
||||||
|
this.stickyElement = el as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const open = this.props.menu.sideBarOpened;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionsContext.Consumer>
|
<OptionsContext.Consumer>
|
||||||
{options => (
|
{options => {
|
||||||
<>
|
const CondFloating = options.parentElement ? PortaledFloatingButton :
|
||||||
<StyledStickySidebar
|
FloatingButton;
|
||||||
open={open}
|
const CondSidebar = options.parentElement && window.innerWidth < 800 ? PortaledStickySidebar :
|
||||||
className={this.props.className}
|
StyledStickySidebar;
|
||||||
style={style(options)}
|
|
||||||
// tslint:disable-next-line
|
return (
|
||||||
ref={el => {
|
<>
|
||||||
this.stickyElement = el as any;
|
<CondSidebar
|
||||||
}}
|
open={open}
|
||||||
>
|
className={this.props.className}
|
||||||
{this.props.children}
|
style={this.getSidebarStyles(options)}
|
||||||
</StyledStickySidebar>
|
ref={this.setStickyElementRef}
|
||||||
<FloatingButton onClick={this.toggleNavMenu}>
|
parentElement={options.parentElement}
|
||||||
<AnimatedChevronButton open={open} />
|
>
|
||||||
</FloatingButton>
|
{this.props.children}
|
||||||
</>
|
</CondSidebar>
|
||||||
)}
|
<CondFloating onClick={this.toggleNavMenu} parentElement={options.parentElement}>
|
||||||
|
<AnimatedChevronButton open={open} />
|
||||||
|
</CondFloating>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
</OptionsContext.Consumer>
|
</OptionsContext.Consumer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ export interface RedocRawOptions {
|
||||||
disableSearch?: boolean | string;
|
disableSearch?: boolean | string;
|
||||||
onlyRequiredInSamples?: boolean | string;
|
onlyRequiredInSamples?: boolean | string;
|
||||||
showExtensions?: boolean | string | string[];
|
showExtensions?: boolean | string | string[];
|
||||||
|
parentElementSelector?: string;
|
||||||
|
|
||||||
unstable_ignoreMimeParameters?: boolean;
|
unstable_ignoreMimeParameters?: boolean;
|
||||||
|
|
||||||
|
@ -106,6 +107,22 @@ export class RedocNormalizedOptions {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static normalizeParentElementSelector(value: RedocRawOptions['parentElementSelector']): Element | null {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
return querySelector(value);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(
|
||||||
|
'Invalid selector for parent element.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
theme: ResolvedThemeInterface;
|
theme: ResolvedThemeInterface;
|
||||||
scrollYOffset: () => number;
|
scrollYOffset: () => number;
|
||||||
hideHostname: boolean;
|
hideHostname: boolean;
|
||||||
|
@ -120,6 +137,7 @@ export class RedocNormalizedOptions {
|
||||||
disableSearch: boolean;
|
disableSearch: boolean;
|
||||||
onlyRequiredInSamples: boolean;
|
onlyRequiredInSamples: boolean;
|
||||||
showExtensions: boolean | string[];
|
showExtensions: boolean | string[];
|
||||||
|
parentElement: Element | null;
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
/* tslint:disable-next-line */
|
||||||
unstable_ignoreMimeParameters: boolean;
|
unstable_ignoreMimeParameters: boolean;
|
||||||
|
@ -147,6 +165,7 @@ export class RedocNormalizedOptions {
|
||||||
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
||||||
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
||||||
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
||||||
|
this.parentElement = RedocNormalizedOptions.normalizeParentElementSelector(raw.parentElementSelector);
|
||||||
|
|
||||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,13 @@ import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
const EVENT = 'scroll';
|
const EVENT = 'scroll';
|
||||||
|
|
||||||
export class ScrollService {
|
export class ScrollService {
|
||||||
private _scrollParent: Window | HTMLElement | undefined;
|
private _scrollParent: Window | HTMLElement | Element | null | undefined;
|
||||||
private _emiter: EventEmitter;
|
private _emiter: EventEmitter;
|
||||||
private _prevOffsetY: number = 0;
|
private _prevOffsetY: number = 0;
|
||||||
|
private _redocContainer: Window | HTMLElement | Element | null | undefined;
|
||||||
constructor(private options: RedocNormalizedOptions) {
|
constructor(private options: RedocNormalizedOptions) {
|
||||||
this._scrollParent = IS_BROWSER ? window : undefined;
|
const browserContainer = options.parentElement || window;
|
||||||
|
this._scrollParent = IS_BROWSER ? browserContainer : undefined;
|
||||||
this._emiter = new EventEmitter();
|
this._emiter = new EventEmitter();
|
||||||
this.bind();
|
this.bind();
|
||||||
}
|
}
|
||||||
|
@ -31,8 +33,8 @@ export class ScrollService {
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollY(): number {
|
scrollY(): number {
|
||||||
if (typeof HTMLElement !== 'undefined' && this._scrollParent instanceof HTMLElement) {
|
if (typeof HTMLElement !== 'undefined' && this._scrollParent instanceof Element && this._redocContainer instanceof Element) {
|
||||||
return this._scrollParent.scrollTop;
|
return this._scrollParent.getBoundingClientRect().top - this._redocContainer.getBoundingClientRect().top;
|
||||||
} else if (this._scrollParent !== undefined) {
|
} else if (this._scrollParent !== undefined) {
|
||||||
return (this._scrollParent as Window).pageYOffset;
|
return (this._scrollParent as Window).pageYOffset;
|
||||||
} else {
|
} else {
|
||||||
|
@ -44,6 +46,10 @@ export class ScrollService {
|
||||||
if (el === null) {
|
if (el === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this._scrollParent instanceof Element) {
|
||||||
|
return el.getBoundingClientRect().top - this._scrollParent.getBoundingClientRect().top > this.options.scrollYOffset();
|
||||||
|
}
|
||||||
|
|
||||||
return el.getBoundingClientRect().top > this.options.scrollYOffset();
|
return el.getBoundingClientRect().top > this.options.scrollYOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +57,11 @@ export class ScrollService {
|
||||||
if (el === null) {
|
if (el === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._scrollParent instanceof Element) {
|
||||||
|
const topDistance = el.getBoundingClientRect().top - this._scrollParent.getBoundingClientRect().top;
|
||||||
|
return (topDistance > 0 ? Math.floor(topDistance) : Math.ceil(topDistance)) <= this.options.scrollYOffset();
|
||||||
|
}
|
||||||
const top = el.getBoundingClientRect().top;
|
const top = el.getBoundingClientRect().top;
|
||||||
return (top > 0 ? Math.floor(top) : Math.ceil(top)) <= this.options.scrollYOffset();
|
return (top > 0 ? Math.floor(top) : Math.ceil(top)) <= this.options.scrollYOffset();
|
||||||
}
|
}
|
||||||
|
@ -76,6 +87,11 @@ export class ScrollService {
|
||||||
this.scrollIntoView(element);
|
this.scrollIntoView(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
setRedocElement(node: HTMLDivElement) {
|
||||||
|
this._redocContainer = node;
|
||||||
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
@Throttle(100)
|
@Throttle(100)
|
||||||
handleScroll() {
|
handleScroll() {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user