mirror of
https://github.com/Redocly/redoc.git
synced 2025-01-31 10:04:08 +03:00
chore: create demo page
This commit is contained in:
parent
28f239114b
commit
83eaa5f838
215
demo/ComboBox.tsx
Normal file
215
demo/ComboBox.tsx
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
/**
|
||||||
|
* Could not find ready-to-use component with required behaviour so
|
||||||
|
* I quickly hacked my own. Will refactor into separate npm package later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import styled, { StyledFunction } from 'styled-components';
|
||||||
|
|
||||||
|
function withProps<T, U extends HTMLElement = HTMLElement>(
|
||||||
|
styledFunction: StyledFunction<React.HTMLProps<U>>,
|
||||||
|
): StyledFunction<T & React.HTMLProps<U>> {
|
||||||
|
return styledFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DropDownItem = withProps<{ active: boolean }>(styled.li)`
|
||||||
|
${props => ((props as any).active ? 'background-color: #eee' : '')};
|
||||||
|
padding: 13px 16px;
|
||||||
|
&:hover {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
cursor: pointer;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DropDownList = styled.ul`
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12),
|
||||||
|
0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 0 0 2px 2px;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 200;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
list-style: none;
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
padding: 5px 0;
|
||||||
|
font-family: 'Lato';
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ComboBoxWrap = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Input = styled.input`
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
color: #555;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
|
||||||
|
font-size: 16px;
|
||||||
|
height: 28px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 1;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #66afe9;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Button = styled.button`
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
padding: 2px 10px;
|
||||||
|
touch-action: manipulation;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-left: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 28px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 1;
|
||||||
|
outline: none;
|
||||||
|
width: 80px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export interface ComboBoxProps {
|
||||||
|
onChange?: (val: string) => void;
|
||||||
|
options: { value: string; label: string }[];
|
||||||
|
placeholder?: string;
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
export interface ComboBoxState {
|
||||||
|
open: boolean;
|
||||||
|
value: string;
|
||||||
|
activeItemIdx: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ComboBox extends React.Component<ComboBoxProps, ComboBoxState> {
|
||||||
|
state = {
|
||||||
|
open: false,
|
||||||
|
value: this.props.value || '',
|
||||||
|
activeItemIdx: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
open = () => {
|
||||||
|
this.setState({
|
||||||
|
open: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
close = () => {
|
||||||
|
this.setState({
|
||||||
|
open: false,
|
||||||
|
activeItemIdx: -1,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = e => {
|
||||||
|
this.updateValue(e.currentTarget.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateValue(value) {
|
||||||
|
this.setState({
|
||||||
|
value,
|
||||||
|
activeItemIdx: -1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelect(value: string) {
|
||||||
|
this.updateValue(value);
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange(value);
|
||||||
|
}
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
this.handleSelect(e.currentTarget.value);
|
||||||
|
} else if (e.keyCode === 40) {
|
||||||
|
const activeItemIdx = Math.min(this.props.options.length - 1, ++this.state.activeItemIdx);
|
||||||
|
this.setState({
|
||||||
|
open: true,
|
||||||
|
activeItemIdx,
|
||||||
|
value: this.props.options[activeItemIdx].value,
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.keyCode === 38) {
|
||||||
|
const activeItemIdx = Math.max(0, --this.state.activeItemIdx);
|
||||||
|
this.setState({
|
||||||
|
activeItemIdx,
|
||||||
|
value: this.props.options[activeItemIdx].value,
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.keyCode === 27) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleBlur = () => {
|
||||||
|
setTimeout(() => this.close(), 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleItemClick = (val, idx) => {
|
||||||
|
this.handleSelect(val);
|
||||||
|
this.setState({
|
||||||
|
activeItemIdx: idx,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { open, value, activeItemIdx } = this.state;
|
||||||
|
const { options, placeholder } = this.props;
|
||||||
|
return (
|
||||||
|
<ComboBoxWrap>
|
||||||
|
<Input
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
value={value}
|
||||||
|
onFocus={this.open}
|
||||||
|
onBlur={this.handleBlur}
|
||||||
|
onKeyDown={this.handleKeyPress}
|
||||||
|
/>
|
||||||
|
<Button onClick={() => this.handleSelect(this.state.value)}> TRY IT </Button>
|
||||||
|
{open && (
|
||||||
|
<DropDownList>
|
||||||
|
{options.map((option, idx) => (
|
||||||
|
<DropDownItem
|
||||||
|
active={idx == activeItemIdx}
|
||||||
|
key={option.value}
|
||||||
|
onMouseDown={() => {
|
||||||
|
this.handleItemClick(option.value, idx);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<small>
|
||||||
|
<strong>{option.label}</strong>
|
||||||
|
</small>
|
||||||
|
<br />
|
||||||
|
{option.value}
|
||||||
|
</DropDownItem>
|
||||||
|
))}
|
||||||
|
</DropDownList>
|
||||||
|
)}
|
||||||
|
</ComboBoxWrap>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>ReDoc</title>
|
<title>ReDoc Interactive Demo</title>
|
||||||
|
<meta name="description" content="ReDoc Interactive Demo. OpenAPI/Swagger-generated API Reference Documentation" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -18,8 +20,21 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<redoc spec-url="./openapi.yaml"></redoc>
|
<div id="container"> </div>
|
||||||
<script src="../bundles/redoc.standalone.js"></script>
|
|
||||||
|
<script>
|
||||||
|
(function (i, s, o, g, r, a, m) {
|
||||||
|
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
|
||||||
|
(i[r].q = i[r].q || []).push(arguments)
|
||||||
|
}, i[r].l = 1 * new Date(); a = s.createElement(o),
|
||||||
|
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
|
||||||
|
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||||
|
|
||||||
|
if (window.location.host === 'rebilly.github.io') {
|
||||||
|
ga('create', 'UA-81703547-1', 'auto');
|
||||||
|
ga('send', 'pageview');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
182
demo/index.tsx
Normal file
182
demo/index.tsx
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { RedocStandalone } from '../';
|
||||||
|
import ComboBox from './ComboBox';
|
||||||
|
import * as url from 'url';
|
||||||
|
|
||||||
|
const demos = [
|
||||||
|
{ value: 'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml', label: 'Instagram' },
|
||||||
|
{
|
||||||
|
value: 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml',
|
||||||
|
label: 'Google Calendar',
|
||||||
|
},
|
||||||
|
{ value: 'https://api.apis.guru/v2/specs/slack.com/1.0.3/swagger.yaml', label: 'Slack' },
|
||||||
|
{ value: 'https://api.apis.guru/v2/specs/zoom.us/2.0.0/swagger.yaml', label: 'Zoom.us' },
|
||||||
|
{
|
||||||
|
value: 'https://api.apis.guru/v2/specs/graphhopper.com/1.0/swagger.yaml',
|
||||||
|
label: 'GraphHopper',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_SPEC = 'openapi.yaml';
|
||||||
|
|
||||||
|
class DemoApp extends React.Component<
|
||||||
|
{},
|
||||||
|
{ specUrl: string; dropdownOpen: boolean; cors: boolean }
|
||||||
|
> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
let parts = window.location.search.match(/url=([^&]+)/);
|
||||||
|
let url = DEFAULT_SPEC;
|
||||||
|
if (parts && parts.length > 1) {
|
||||||
|
url = decodeURIComponent(parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = window.location.search.match(/[?&]nocors(&|#|$)/);
|
||||||
|
let cors = true;
|
||||||
|
if (parts && parts.length > 1) {
|
||||||
|
cors = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
specUrl: url,
|
||||||
|
dropdownOpen: false,
|
||||||
|
cors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (url: string) => {
|
||||||
|
this.setState({
|
||||||
|
specUrl: url,
|
||||||
|
});
|
||||||
|
window.history.pushState(
|
||||||
|
undefined,
|
||||||
|
'',
|
||||||
|
updateQueryStringParameter(location.search, 'url', url),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleCors = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const cors = e.currentTarget.checked;
|
||||||
|
this.setState({
|
||||||
|
cors: cors,
|
||||||
|
});
|
||||||
|
window.history.pushState(
|
||||||
|
undefined,
|
||||||
|
'',
|
||||||
|
updateQueryStringParameter(location.search, 'nocors', cors ? undefined : ''),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { specUrl, cors } = this.state;
|
||||||
|
let proxiedUrl = specUrl;
|
||||||
|
if (specUrl !== DEFAULT_SPEC) {
|
||||||
|
proxiedUrl = cors
|
||||||
|
? '\\\\cors.apis.guru/' + url.resolve(window.location.href, specUrl)
|
||||||
|
: specUrl;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Heading>
|
||||||
|
<a href=".">
|
||||||
|
<Logo src="https://github.com/Rebilly/ReDoc/raw/master/docs/images/redoc-logo.png" />
|
||||||
|
</a>
|
||||||
|
<ControlsContainer>
|
||||||
|
<ComboBox
|
||||||
|
placeholder={'URL to a spec to try'}
|
||||||
|
options={demos}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
value={specUrl === DEFAULT_SPEC ? '' : specUrl}
|
||||||
|
/>
|
||||||
|
<CorsCheckbox title="Use CORS proxy">
|
||||||
|
<input id="cors_checkbox" type="checkbox" onChange={this.toggleCors} checked={cors} />
|
||||||
|
<label htmlFor="cors_checkbox">CORS</label>
|
||||||
|
</CorsCheckbox>
|
||||||
|
</ControlsContainer>
|
||||||
|
<iframe
|
||||||
|
src="https://ghbtns.com/github-btn.html?user=Rebilly&repo=ReDoc&type=star&count=true&size=large"
|
||||||
|
frameBorder="0"
|
||||||
|
scrolling="0"
|
||||||
|
width="150px"
|
||||||
|
height="30px"
|
||||||
|
/>
|
||||||
|
</Heading>
|
||||||
|
<RedocStandalone specUrl={proxiedUrl} options={{ scrollYOffset: 'nav' }} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Styled components ====== */
|
||||||
|
|
||||||
|
const ControlsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 15px;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CorsCheckbox = styled.div`
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Heading = styled.nav`
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
height: 50px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid #cccccc;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-family: 'Lato';
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Logo = styled.img`
|
||||||
|
height: 40px;
|
||||||
|
width: 124px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 15px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
render(<DemoApp />, document.getElementById('container'));
|
||||||
|
|
||||||
|
/* ====== Helpers ====== */
|
||||||
|
function updateQueryStringParameter(uri, key, value) {
|
||||||
|
const keyValue = value === '' ? key : key + '=' + value;
|
||||||
|
var re = new RegExp('([?|&])' + key + '=?.*?(&|#|$)', 'i');
|
||||||
|
if (uri.match(re)) {
|
||||||
|
if (value !== undefined) {
|
||||||
|
return uri.replace(re, '$1' + keyValue + '$2');
|
||||||
|
} else {
|
||||||
|
return uri.replace(re, (_, separator: string, rest: string) => {
|
||||||
|
if (rest.startsWith('&')) {
|
||||||
|
rest = rest.substring(1);
|
||||||
|
}
|
||||||
|
return separator === '&' ? rest : separator + rest;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value === undefined) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
var hash = '';
|
||||||
|
if (uri.indexOf('#') !== -1) {
|
||||||
|
hash = uri.replace(/.*#/, '#');
|
||||||
|
uri = uri.replace(/#.*/, '');
|
||||||
|
}
|
||||||
|
var separator = uri.indexOf('?') !== -1 ? '&' : '?';
|
||||||
|
return uri + separator + keyValue + hash;
|
||||||
|
}
|
||||||
|
}
|
73
demo/webpack.config.ts
Normal file
73
demo/webpack.config.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import * as webpack from 'webpack';
|
||||||
|
import * as HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||||
|
|
||||||
|
const VERSION = JSON.stringify(require('../package.json').version);
|
||||||
|
const REVISION = JSON.stringify(
|
||||||
|
require('child_process')
|
||||||
|
.execSync('git rev-parse --short HEAD')
|
||||||
|
.toString()
|
||||||
|
.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
entry: __dirname + '/index.tsx',
|
||||||
|
output: {
|
||||||
|
filename: 'redoc-demo.bundle.js',
|
||||||
|
path: __dirname + '/dist',
|
||||||
|
},
|
||||||
|
|
||||||
|
devServer: {
|
||||||
|
contentBase: __dirname,
|
||||||
|
watchContentBase: true,
|
||||||
|
port: 8081,
|
||||||
|
stats: 'errors-only',
|
||||||
|
},
|
||||||
|
|
||||||
|
devtool: 'eval',
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.tsx', '.js', '.json'],
|
||||||
|
},
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' },
|
||||||
|
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: [
|
||||||
|
'react-hot-loader/webpack',
|
||||||
|
{
|
||||||
|
loader: 'awesome-typescript-loader',
|
||||||
|
options: {
|
||||||
|
module: 'es2015',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exclude: ['node_modules'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: {
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: true,
|
||||||
|
minimize: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
__REDOC_VERSION__: VERSION,
|
||||||
|
__REDOC_REVISION__: REVISION,
|
||||||
|
__REDOC_DEV__: false,
|
||||||
|
}),
|
||||||
|
new webpack.NamedModulesPlugin(),
|
||||||
|
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: 'demo/index.html',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
|
@ -19,7 +19,8 @@
|
||||||
"prettier": "prettier --write \"src/**/*.{ts,tsx}\"",
|
"prettier": "prettier --write \"src/**/*.{ts,tsx}\"",
|
||||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
|
||||||
"lint": "tslint --project tsconfig.json",
|
"lint": "tslint --project tsconfig.json",
|
||||||
"benchmark": "node ./benchmark/benchmark.js"
|
"benchmark": "node ./benchmark/benchmark.js",
|
||||||
|
"start:demo": "webpack-dev-server --config demo/webpack.config.ts"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user