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