diff --git a/_override/App.tsx b/_override/App.tsx
new file mode 100644
index 00000000..05fbd344
--- /dev/null
+++ b/_override/App.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import type { Location } from 'history';
+
+/**
+ * Root wrapper.
+ */
+export function App({ children, location }: React.PropsWithChildren<{ location: Location }>) {
+ return (
+ <>
+ {/* you can add some top-level providers here or anything to wrap whole app */}
+
+ {/* */}
+
+ {children}
+ >
+ );
+}
\ No newline at end of file
diff --git a/_override/Footer.tsx b/_override/Footer.tsx
new file mode 100644
index 00000000..f68ecec7
--- /dev/null
+++ b/_override/Footer.tsx
@@ -0,0 +1,149 @@
+import * as React from 'react';
+import styled from 'styled-components';
+
+import { Box, Flex, Link, FooterProps } from '@redocly/developer-portal/ui';
+
+/**
+ * Custom Navbar. The implementation below is almost identical to our default Footer
+ */
+export default function CustomFooter(props: FooterProps) {
+ // you can use columns/copyright values from props, it comes from siteConfig.yaml
+ // but you can also import it from a separate yaml or json file
+ const { columns, copyrightText } = props.footer;
+ const siteVersion = props.siteVersion;
+
+ const columnsElement = columns.length ? (
+
+
+ {columns.map((column, index) => (
+
+ {column.group || column.label}
+
+ {column.items.map((columnItem, columnItemInex) =>
+ columnItem.type === 'separator' ? (
+ {columnItem.label}
+ ) : (
+
+
+ {columnItem.label}
+
+
+ )
+ )}
+
+
+ ))}
+
+
+ ) : null;
+
+ const infoElement =
+ copyrightText || siteVersion ? (
+
+
+ {copyrightText} {siteVersion ? `| ${siteVersion}` : null}
+
+
+ ) : null;
+
+ return (
+
+ {columnsElement}
+ {infoElement}
+
+ );
+}
+
+const FooterColumns = styled(Flex)`
+ background-color: ${({ theme }) => theme.colors.footer.main};
+ color: ${({ theme }) => theme.colors.footer.contrastText};
+ font-family: ${({ theme }) => theme.typography.headings.fontFamily};
+
+ a,
+ a:hover {
+ color: ${({ theme }) => theme.colors.footer.contrastText};
+ }
+`;
+
+// very important for NavWrapper to be a "footer" HTML tag
+const FooterWrapper = styled.footer`
+ font-size: 1rem;
+ flex-shrink: 0;
+ font-family: ${({ theme }) => theme.typography.fontFamily};
+
+ @media print {
+ color: black;
+ ${FooterColumns} {
+ display: none;
+ }
+ }
+`;
+
+export const FooterInfo = styled.section`
+ display: flex;
+ justify-content: center;
+ font-size: 0.875em;
+ padding: 1.5em 3em;
+ font-weight: ${({ theme }) => theme.typography.fontWeightBold};
+ background-color: ${({ theme }) => theme.colors.footer.main};
+ color: ${({ theme }) => theme.colors.footer.contrastText};
+ span {
+ max-width: 1200px;
+ }
+`;
+
+export const ColumnList = styled.ul`
+ margin: 0;
+ padding: 0;
+ list-style: none;
+`;
+
+export const ColumnListItem = styled.li`
+ font-weight: ${({ theme }) => theme.typography.fontWeightBold};
+ padding-bottom: 0.75em;
+ a {
+ color: ${props => props.theme.colors.primary.contrastText};
+ text-decoration: none;
+ }
+`;
+
+export const FooterSeparator = styled.li`
+ opacity: 0.75;
+ margin: 10px 0 5px 0;
+ font-size: 0.75em;
+ text-transform: uppercase;
+ font-family: ${({ theme }) => theme.typography.headings.fontFamily};
+`;
+
+export const FooterCopyright = styled.span`
+ text-align: center;
+`;
+
+export const ColumnTitle = styled.span`
+ display: inline-block;
+ font-weight: ${({ theme }) => theme.typography.fontWeightRegular};
+ margin-bottom: 1.5em;
+ fontfamily: ${({ theme }) => theme.typography.headings.fontFamily};
+
+ @media only screen and (min-width: ${({ theme }) => theme.breakpoints.large}) {
+ margin-bottom: 2.5em;
+ }
+`;
diff --git a/_override/MenuItem.tsx b/_override/MenuItem.tsx
new file mode 100644
index 00000000..85d07b8a
--- /dev/null
+++ b/_override/MenuItem.tsx
@@ -0,0 +1,266 @@
+import * as React from 'react';
+import styled, { useTheme } from 'styled-components';
+import { MenuItemProps, Link, Arrow, OperationBadge, MenuItem } from '@redocly/developer-portal/ui';
+
+/**
+ * You can simply wrap or add css to the existing MenuItem element by importing it
+ */
+
+// import { MenuItem as OriginalMenuItem } from '@redocly/developer-portal/ui';
+
+// export default function CustomMenuItem(props: MenuItemProps) {
+// return (
+//
+//
+//
+// );
+// }
+
+// const StyledMenuItem = styled(OriginalMenuItem)`
+// color: #555;
+// `;
+
+/**
+ * Custom sidebar MenuItem. The implementation below is almost identical to our default Footer.
+ * The only difference is it adds a red border for active item.
+ */
+export default function CustomMenuItem(props: MenuItemProps) {
+ const {
+ item: { active, expanded, items, link, label, type, httpVerb, external, target, menuStyle, icon, sublabel },
+ depth,
+ isLast,
+ isExpanded,
+ isAlwaysExpanded,
+ className,
+ } = props;
+
+ const theme = useTheme();
+ const ref = React.useRef(null);
+
+ React.useLayoutEffect(() => {
+ if (props.item.active) {
+ const node = ref.current;
+ if (!node) {
+ return;
+ }
+
+ const scrollHeight = document.body.scrollHeight;
+ const totalHeight = window.scrollY + window.innerHeight;
+
+ // at the bottom of the page
+ if (totalHeight >= scrollHeight) {
+ return;
+ }
+
+ node.scrollIntoView({
+ behavior: 'smooth',
+ block: 'nearest',
+ });
+ }
+ });
+
+ const hasChildren = items && items.length > 0;
+ const arrowDirection = isExpanded ? 'down' : 'right';
+ const isDrilldown = menuStyle === 'drilldown';
+ const element =
+ type === 'separator' ? (
+
+ {label}
+
+ ) : (
+ <>
+
+ {hasChildren && !isDrilldown && !isAlwaysExpanded && (
+
+
+
+ )}
+ {httpVerb && (
+
+ {httpVerb}
+
+ )}
+ {isDrilldown ? (
+
+ ) : (
+ {label}
+ )}
+ {/* {external && (
+
+
+
+ )} */}
+
+ >
+ );
+
+ return link ? (
+
+ {element}
+
+ ) : (
+ element
+ );
+}
+
+export const MenuItemTitle = styled.div<{
+ expanded: boolean;
+ active: boolean;
+ last: boolean;
+ isAlwaysExpanded: boolean;
+ hasChildren: boolean;
+ isDrilldown: boolean;
+ depth: number;
+}>`
+ border-left: ${({ active }) => (active ? '4px solid #DC1928' : '4px solid transparent')};
+
+ position: relative;
+ display: flex;
+ justify-content: space-between;
+
+ font-family: ${({ theme }) => theme.sidebar.fontFamily};
+ font-size: ${({ theme }) => theme.sidebar.fontSize};
+ font-weight: ${({ theme }) => theme.typography.fontWeightRegular};
+
+ color: ${props => (props.active ? props.theme.sidebar.activeTextColor : 'inherit')};
+ background-color: ${props => (props.active ? props.theme.sidebar.activeBgColor : 'inherit')};
+
+ cursor: ${({ isAlwaysExpanded }) => (isAlwaysExpanded ? 'default' : 'pointer')};
+
+ margin-left: ${({ depth, theme }) =>
+ `${theme.sidebar.spacing.offsetLeft + depth * theme.sidebar.spacing.offsetNesting}px`};
+
+ padding-top: ${({ theme }) => theme.sidebar.spacing.paddingVertical}px;
+ padding-right: ${({ theme }) => theme.sidebar.spacing.paddingHorizontal}px;
+ padding-bottom: ${({ theme }) => theme.sidebar.spacing.paddingVertical}px;
+ margin-bottom: 1px; // hardcoded for now
+
+ padding-left: ${
+ ({ theme, hasChildren, isAlwaysExpanded }) =>
+ hasChildren && !isAlwaysExpanded
+ ? theme.sidebar.spacing.paddingHorizontal
+ : theme.sidebar.spacing.paddingHorizontal +
+ theme.sidebar.caretSize +
+ theme.sidebar.spacing.unit * 0.5 /* chevron margin-right = unit 0.5*/
+ }px;
+
+ transition: background-color 0.3s, color 0.3s;
+ border-top-left-radius: ${({ theme }) => theme.sidebar.borderRadius};
+ border-bottom-left-radius: ${({ theme }) => theme.sidebar.borderRadius};
+
+ word-break: break-word;
+
+ :hover {
+ color: ${({ isAlwaysExpanded, theme }) => (isAlwaysExpanded ? 'inherit' : theme.sidebar.activeTextColor)};
+ background-color: ${({ isAlwaysExpanded, theme }) => (isAlwaysExpanded ? 'inherit' : theme.sidebar.activeBgColor)};
+ }
+
+ :empty {
+ padding: 0;
+ }
+
+ ${({ isDrilldown, theme }) =>
+ isDrilldown &&
+ `
+ padding-top: ${theme.sidebar.spacing.paddingVertical * 2}px;
+ padding-bottom: ${theme.sidebar.spacing.paddingVertical * 2}px;
+ padding-left: ${theme.sidebar.spacing.paddingHorizontal}px;
+ &:hover {
+ color: currentColor;
+ background-color: ${theme.sidebar.backgroundColor};
+ }
+ `}
+`;
+
+export const Separator = styled.span<{ depth?: number }>`
+ display: flex;
+ justify-content: space-between;
+ margin-left: ${({ theme, depth = 0 }) =>
+ theme.sidebar.spacing.offsetLeft + depth * theme.sidebar.spacing.offsetNesting}px;
+ padding: ${({ theme }) => theme.sidebar.spacing.paddingVertical}px
+ ${({ theme }) => theme.sidebar.spacing.paddingHorizontal}px;
+
+ padding-left: ${
+ ({ theme }) =>
+ theme.sidebar.spacing.paddingHorizontal + theme.sidebar.spacing.unit * 1.5 /* 1 chevron width + 0.5 margin-right*/
+ }px;
+ position: relative;
+ cursor: default;
+ font-family: ${({ theme }) => theme.sidebar.fontFamily};
+ font-size: ${({ theme }) => theme.sidebar.fontSize};
+ font-weight: ${({ theme }) => theme.typography.fontWeightBold};
+ color: ${({ theme }) => theme.sidebar.separatorLabelColor};
+
+ &:empty {
+ padding: 0.1em 0;
+ }
+`;
+
+const MenuLabel = styled.span`
+ width: 100%;
+`;
+
+const ArrowWrapper = styled.div`
+ margin-right: 5px;
+`;
+
+const StyledDrilldownMenuItem = styled.span`
+ display: inline-flex;
+ align-items: center;
+ color: ${({ theme }) => theme.sidebar.textColor};
+ font-size: ${({ theme }) => theme.sidebar.fontSize};
+ font-weight: ${({ theme }) => theme.typography.fontWeightBold};
+`;
+
+const DrilldownMenuIcon = styled.img`
+ width: ${({ theme }) => theme.sidebar.spacing.unit * 4}px;
+ height: ${({ theme }) => theme.sidebar.spacing.unit * 4}px;
+ margin-right: ${({ theme }) => theme.sidebar.spacing.unit}px;
+ border-radius: 50%;
+ flex-shrink: 0;
+ overflow: hidden;
+`;
+
+const DrilldownMenuSublabel = styled.span`
+ display: block;
+ margin-top: 2px;
+ color: ${({ theme }) => theme.sidebar.textColorSecondary};
+ font-size: 0.85rem;
+ font-weight: ${({ theme }) => theme.typography.fontWeightRegular};
+`;
+
+// const StyledOperationBadge = styled(OperationBadge)`
+// flex-shrink: 0;
+// margin-top: 0;
+// `;
+
+function DrilldownMenuItem(props: { icon?: string | null; label?: string | null; sublabel?: string | null }) {
+ const { icon, label, sublabel } = props;
+ return (
+
+ {icon && }
+
+ {label}
+ {sublabel && {sublabel}}
+
+
+ );
+}
diff --git a/_override/NavBar.tsx b/_override/NavBar.tsx
new file mode 100644
index 00000000..d5d679f7
--- /dev/null
+++ b/_override/NavBar.tsx
@@ -0,0 +1,190 @@
+import * as React from 'react';
+import styled from 'styled-components';
+
+import { Flex, Link, NavBarProps, SearchBox } from '@redocly/developer-portal/ui';
+
+/**
+ * Custom Navbar. The implementation below is almost identical to our default Navbar
+ */
+export default function CustomNavBar(props: NavBarProps) {
+ // you can use items values from props, it comes from siteConfig.yaml
+ // but you can also import it from a separate yaml or json file
+ const { items, logo, href, altText, location } = props;
+
+ const [isMobileMenuOpened, setMobileMenuOpened] = React.useState(false);
+ const toggleMobileMenu = () => setMobileMenuOpened(!isMobileMenuOpened);
+ const hideMobileMenu = () => setMobileMenuOpened(false);
+
+
+ const navItems = items
+ .filter(item => item.type !== 'search')
+ .map((item, index) => {
+ return (
+
+ {item.label}
+
+ );
+ });
+
+ return (
+
+
+
+
+
+
+ {navItems}
+
+
+
+
+
+
+
+
+ {navItems}
+
+
+
+ );
+}
+
+// very important for NavWrapper to be a "nav" HTML tag
+export const NavWrapper = styled.nav`
+ display: flex;
+ color: ${({ theme }) => theme.colors.navbar.contrastText};
+ align-items: center;
+ justify-content: space-between;
+ flex-shrink: 0;
+
+ background: ${({ theme }) => `linear-gradient( -63.43000000000001deg,
+ ${theme.colors.navbar.main} 15%,
+ ${theme.colors.navbar.gradient} 85%)`};
+
+ font-size: 1rem;
+ position: sticky;
+ top: 0;
+ z-index: 200;
+ padding: 1.125em 2.75em;
+ font-family: ${({ theme }) => theme.typography.headings.fontFamily};
+
+ @media only screen and (max-width: ${({ theme }) => theme.breakpoints.medium}) {
+ padding: 1.25em;
+ }
+
+ @media only screen and (min-width: ${({ theme }) => theme.breakpoints.medium}) and (max-width: ${({ theme }) =>
+ theme.breakpoints.large}) {
+ font-size: 0.875rem;
+ }
+`;
+
+const NavItems = styled.ul`
+ display: none;
+ margin: 0 0 0 40px;
+ padding: 0;
+ align-items: center;
+ justify-content: start;
+ & li {
+ list-style: none;
+ margin-right: 20px;
+ & a {
+ color: #ffffff;
+ text-decoration: none;
+ }
+ }
+ @media only screen and (min-width: ${({ theme }) => theme.breakpoints.medium}) {
+ display: flex;
+ }
+`;
+
+const NavItem = styled.li`
+ padding: 10px 0;
+`;
+
+export const MobileMenu = styled.ul<{ isShown: boolean }>`
+ background: ${props => props.theme.colors.primary.main};
+ list-style: none;
+ padding: 50px 40px;
+ margin: 0;
+ position: absolute;
+ border-top: 1px solid transparent;
+ z-index: 100;
+ color: ${props => props.theme.colors.primary.contrastText};
+ top: 0;
+ right: 0;
+ left: 0;
+ bottom: 0;
+ font-size: 1.1875rem;
+ box-shadow: 0px 10px 100px 0px rgba(35, 35, 35, 0.1);
+ text-align: left;
+ display: none;
+ @media only screen and (max-width: ${({ theme }) => theme.breakpoints.medium}) {
+ position: fixed;
+ display: ${props => (props.isShown ? 'flex' : 'none')};
+ flex-direction: column;
+ overflow-y: auto;
+ }
+ & li {
+ list-style: none;
+ margin-right: 20px;
+ & a {
+ color: #ffffff;
+ text-decoration: none;
+ }
+ }
+`;
+
+export const NavControls = styled.div`
+ padding: 10px;
+ display: flex;
+ align-items: center;
+ flex: 1;
+ justify-content: flex-end;
+ @media only screen and (min-width: ${({ theme }) => theme.breakpoints.medium}) {
+ display: none;
+ }
+`;
+
+export const MobileMenuIcon = styled.span`
+ width: 1.25em;
+ height: 1.25em;
+ display: inline-block;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' x='0' y='0' viewBox='0 0 396.7 396.7' xml:space='preserve'%3E%3Cpath fill='white' d='M17 87.8h362.7c9.4 0 17-7.6 17-17s-7.6-17-17-17H17c-9.3 0-17 7.7-17 17C0 80.2 7.7 87.8 17 87.8zM17 215.3h362.7c9.4 0 17-7.6 17-17s-7.6-17-17-17H17c-9.3 0-17 7.7-17 17S7.7 215.3 17 215.3zM17 342.8h362.7c9.4 0 17-7.6 17-17s-7.6-17-17-17H17c-9.3 0-17 7.7-17 17S7.7 342.8 17 342.8z'/%3E%3C/svg%3E");
+ cursor: pointer;
+ @media only screen and (min-width: ${({ theme }) => theme.breakpoints.medium}) {
+ display: none;
+ }
+`;
+
+export const CloseIcon = styled.i`
+ cursor: pointer;
+ position: absolute;
+ right: 20px;
+ top: 25px;
+ width: 15px;
+ height: 15px;
+ background-repeat: no-repeat;
+ background-size: 15px 15px;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' viewBox='0 0 15.6 15.6' enable-background='new 0 0 15.642 15.642'%3E%3Cpath fill-rule='evenodd' fill='white' d='M8.9 7.8l6.5-6.5c0.3-0.3 0.3-0.8 0-1.1 -0.3-0.3-0.8-0.3-1.1 0L7.8 6.8 1.3 0.2c-0.3-0.3-0.8-0.3-1.1 0 -0.3 0.3-0.3 0.8 0 1.1l6.5 6.5L0.2 14.4c-0.3 0.3-0.3 0.8 0 1.1 0.1 0.1 0.3 0.2 0.5 0.2s0.4-0.1 0.5-0.2l6.5-6.5 6.5 6.5c0.1 0.1 0.3 0.2 0.5 0.2 0.2 0 0.4-0.1 0.5-0.2 0.3-0.3 0.3-0.8 0-1.1L8.9 7.8z'/%3E%3C/svg%3E");
+`;
+
+export const Logo = styled.img`
+ cursor: pointer;
+ width: auto;
+ height: ${({ theme }) => theme.logo.height};
+ max-width: ${({ theme }) => theme.logo.maxWidth};
+ max-height: ${({ theme }) => theme.logo.maxHeight};
+
+ margin: ${({ theme }) => theme.logo.margin};
+`;
+
+export const LogoLink = styled(Link)`
+ display: inline-block;
+ margin-right: 2.75em;
+`;
diff --git a/_override/SearchInput.tsx b/_override/SearchInput.tsx
new file mode 100644
index 00000000..2341b828
--- /dev/null
+++ b/_override/SearchInput.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react';
+
+import {
+ SearchInputProps,
+ SearchIcon,
+ SearchClearIcon,
+ SearchLoadingIcon,
+ SearchInputField,
+ SearchInputWrap,
+} from '@redocly/developer-portal/ui';
+
+/**
+ * Custom Search Input. The implementation below is almost identical to our default SearchInput.
+ */
+export default function CustomSearchInput(props: SearchInputProps) {
+ const { query, onChange, onToggleSearchResults, onKeyDown, onClear, loading } = props;
+
+ const inputRef = React.useRef(null);
+
+ const [isFocused, setIsFocused] = React.useState(false);
+
+ React.useEffect(() => {
+ if (isFocused && query.length > 0) {
+ onToggleSearchResults(true);
+ } else {
+ onToggleSearchResults(false);
+ }
+ }, [isFocused, query]);
+
+ return (
+
+ setIsFocused(true)}
+ onBlur={() => setIsFocused(false)}
+ onKeyDown={onKeyDown}
+ />
+ {isFocused ? (
+ loading ? (
+
+ ) : (
+
+ )
+ ) : (
+ inputRef.current && inputRef.current.focus()} />
+ )}
+
+ );
+}
diff --git a/_override/SearchResults.tsx b/_override/SearchResults.tsx
new file mode 100644
index 00000000..d365c7fc
--- /dev/null
+++ b/_override/SearchResults.tsx
@@ -0,0 +1,111 @@
+import * as React from 'react';
+import styled from 'styled-components';
+import useOnClickOutside from 'use-onclickoutside';
+
+import { SearchResultItem, SearchResultsWrap, SearchResultsProps } from '@redocly/developer-portal/ui';
+
+const MAX_ITEMS_PER_GROUP = 5;
+
+/**
+ * Custom User Search results. The implementation below is similar to our default search results but
+ * implements simple grouping by URL
+ */
+
+export default function CustomSearchResults(props: SearchResultsProps) {
+ const { show, results, indexError, activeItemIdx, loading, query, onSearchResultsItemClick, onToggleSearchResults } = props;
+
+ const ref = React.useRef(null);
+
+ useOnClickOutside(ref, () => onToggleSearchResults(false));
+
+ function isApiResult(item) {
+ return !!item.httpVerb && item.link.startsWith('/openapi'); // this is example
+ }
+
+ const apiResults = {
+ title: 'API Documentation',
+ results: results.filter(isApiResult).slice(0, MAX_ITEMS_PER_GROUP),
+ };
+
+ const otherResults = {
+ title: 'Other Results',
+ results: results.filter(item => !isApiResult(item)).slice(0, MAX_ITEMS_PER_GROUP),
+ };
+
+ let currentPathname = typeof location !== 'undefined' ? location.pathname : '/';
+
+ // order results differently based on current page
+ const searchGroups =
+ currentPathname && currentPathname.startsWith('/openapi') ? [apiResults, otherResults] : [otherResults, apiResults];
+
+ return (
+
+ {indexError && process.env.NODE_ENV === 'development' && (
+
+ Failed to load search index. Search index is not working in develop.
+ Run yarn build
to build the search index first (requires license key).
+
+ )}
+ {results.length === 0 && !loading && Nothing Found }
+
+
+ {/* our default implementation iterates over the results directly */}
+ {/*
+ {results.map((item, idx) => (
+
+ ))}
+ */}
+
+ {searchGroups.map((group, index) => {
+ return group.results.length ? (
+
+ {group.title}
+ {group.results.map((item, idx) => (
+
+
+
+ ))}
+
+ ) : null;
+ })}
+
+
+ );
+}
+
+// to support keyboard navigation
+const countIndex = (index, idx, searchGroups) => {
+ if (index === 0) {
+ return idx;
+ } else if (index === 1) {
+ return searchGroups[0].results.length + idx;
+ }
+};
+
+const Message = styled.div`
+ text-align: center;
+ color: ${props => props.theme.colors.primary.main};
+ padding: 1.5em;
+ @media only screen and (max-width: ${({ theme }) => theme.breakpoints.medium}) {
+ padding: 0.75em;
+ }
+ color: #212129;
+`;
+
+const SearchCatTitle = styled.div`
+ color: #919194;
+ padding: 16px 24px;
+ text-transform: uppercase;
+ font-size: 14px;
+`;
\ No newline at end of file
diff --git a/_override/layouts/Markdown.tsx b/_override/layouts/Markdown.tsx
new file mode 100644
index 00000000..33aef68a
--- /dev/null
+++ b/_override/layouts/Markdown.tsx
@@ -0,0 +1,68 @@
+import * as React from 'react';
+import { MarkdownLayout as OriginalMarkdownLayout, MarkdownLayoutProps } from '@redocly/developer-portal/ui';
+import styled from 'styled-components';
+
+/**
+ * Layout for markdown content. The implementation below modifies layout based on the location.pathname.
+ */
+
+export default function CustomMarkdownLayout(props: MarkdownLayoutProps) {
+ const { withToc, lastModifiedAgo, Toc, children, location } = props;
+
+ if (location.pathname.endsWith('/blog/')) {
+ // render modified layout if pathname starts with /blog
+
+ // You get markdown page content in {children}.
+ // The code below is showing our default Markdown layout.
+ // But it can be replaced completely.
+
+ return (
+
+
+ {lastModifiedAgo && (
+
+ Last updated {lastModifiedAgo}
+
+ )}
+ {children}
+
+
+
+ );
+ }
+
+ // render default layout otherwise
+ return ;
+}
+
+const PageWrapper = styled.div`
+ display: flex;
+ flex: 1;
+ width: 100%;
+`;
+
+const PageInfo = styled.div`
+ display: flex;
+ align-items: center;
+ font-size: 0.8125rem;
+ margin-bottom: 1.5em;
+`;
+
+const PageInfoText = styled.span`
+ color: ${props => props.theme.colors.text.primary};
+ font-weight: ${props => props.theme.typography.fontWeightRegular};
+ font-family: ${({ theme }) => theme.typography.fontFamily};
+`;
+
+const ContentWrapper = styled.section<{ withToc: boolean }>`
+ width: 90%;
+ margin: 25px auto;
+ @media only screen and (min-width: ${({ theme }) => theme.breakpoints.large}) {
+ max-width: 910px;
+ }
+ @media only screen and (min-width: ${({ theme }) => theme.breakpoints.medium}) {
+ width: ${({ withToc, theme }) => (withToc ? `calc(90% - ${theme.tocPanel.width})` : '90%')};
+ padding-top: 40px;
+ padding-bottom: 40px;
+ }
+`;
diff --git a/_override/layouts/Sidebar.tsx b/_override/layouts/Sidebar.tsx
new file mode 100644
index 00000000..2f0d8f41
--- /dev/null
+++ b/_override/layouts/Sidebar.tsx
@@ -0,0 +1,38 @@
+import * as React from 'react';
+import { SidebarLayout as OriginalSidebarLayout, SidebarProps } from '@redocly/developer-portal/ui';
+
+import styled, { createGlobalStyle } from 'styled-components';
+
+/**
+ * Layout with sidebar. The implementation below wraps original sidebar layout adding paper-like UI.
+ */
+
+export default function CustomSidebarLayout(props: SidebarProps) {
+ return (
+
+
+
+ {props.children} {/* page contents in props.children */}
+
+
+ );
+}
+
+const BodyBg = createGlobalStyle`
+ body {
+ background-color: #eeeeee;
+ }
+`;
+
+const SidebarWrapper = styled.div`
+ width: 90%;
+ max-width: 1400px;
+ margin: 20px auto;
+ padding: 10px 0px;
+ border-radius: 10px;
+ background-color: white;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
+ @media only screen and (min-width: ${({ theme }) => theme.breakpoints.medium}) {
+ width: 100%;
+ }
+`;
\ No newline at end of file
diff --git a/package.json b/package.json
index a59676fa..12bb5c76 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,6 @@
"clean": "redocly-portal clean"
},
"dependencies": {
- "@redocly/developer-portal": "1.1.0-beta.101"
+ "@redocly/developer-portal": "1.1.0-beta.104"
}
}
diff --git a/yarn.lock b/yarn.lock
index 0e966ef4..d7bd505d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1710,17 +1710,17 @@
require-from-string "^2.0.2"
uri-js "^4.2.2"
-"@redocly/developer-portal@1.1.0-beta.101":
- version "1.1.0-beta.101"
- resolved "https://registry.yarnpkg.com/@redocly/developer-portal/-/developer-portal-1.1.0-beta.101.tgz#db84d816bc9e1aab59968019719acebcdd554079"
- integrity sha512-GMTy4L97kHpN+APU7zqrLOMf12Lqt+CtUPvrmtaXWqr0iLT35heH/k/miu4Bmeb9Vp9lRBB6dP2Xk2xhsKnGlA==
+"@redocly/developer-portal@1.1.0-beta.104":
+ version "1.1.0-beta.104"
+ resolved "https://registry.yarnpkg.com/@redocly/developer-portal/-/developer-portal-1.1.0-beta.104.tgz#256f7aa330a533ce9717108ab2129bedf5997ae8"
+ integrity sha512-tGJo6nKreoafpTyGycT7PsVySwTV1QgWuRlAZ4FqPevzMRj08NlVz4ndxcM5rYQ0jSMq5iELBXymEq3WWsihWg==
dependencies:
"@mdx-js/mdx" "0.19.0"
"@mdx-js/tag" "0.20.3"
"@redocly/gatsby-plugin-manifest" "3.3.2"
"@redocly/gatsby-remark-images" "4.2.0"
- "@redocly/openapi-core" "1.0.0-beta.102"
- "@redocly/reference-docs" "2.25.1"
+ "@redocly/openapi-core" "1.0.0-beta.103"
+ "@redocly/reference-docs" "2.26.3"
babel-plugin-react-html-attrs "^3.0.5"
babel-plugin-styled-components "^1.12.0"
buffer "^6.0.3"
@@ -1863,10 +1863,10 @@
unist-util-select "^1.5.0"
unist-util-visit-parents "^2.1.2"
-"@redocly/openapi-core@1.0.0-beta.102":
- version "1.0.0-beta.102"
- resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.102.tgz#e1cd049979f05812c594063fec71e618201319c4"
- integrity sha512-3Fr3fg+9VEF4+4uoyvOOk+9ipmX2GYhlb18uZbpC4v3cUgGpkTRGZM2Qetfah7Tgx2LgqLuw8A1icDD6Zed2Gw==
+"@redocly/openapi-core@1.0.0-beta.103":
+ version "1.0.0-beta.103"
+ resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.103.tgz#fff3fd5071d718cff6f135fe9ca3d0f154551302"
+ integrity sha512-O9ln5sQycdZsHsNbe3eZzDzi/h5mC94mCSZGrx2NbMXJNOqXOuNtG5si5Eamx/1BZgQLL+KhKCVea3QvWyjEcQ==
dependencies:
"@redocly/ajv" "^8.6.4"
"@types/node" "^14.11.8"
@@ -1895,15 +1895,14 @@
pluralize "^8.0.0"
yaml-ast-parser "0.0.43"
-"@redocly/reference-docs@2.25.1":
- version "2.25.1"
- resolved "https://registry.yarnpkg.com/@redocly/reference-docs/-/reference-docs-2.25.1.tgz#ca27a501c05bb217acc889c05d361a09faa96ccd"
- integrity sha512-D0DB7zj/O0LKYKVmqwZYVc5p7Q+wehaD4nWX2A45U75W85cQiUuKal5Mp+Wk0COk+suMvyLnd3NeYK13RWd6DQ==
+"@redocly/reference-docs@2.26.3":
+ version "2.26.3"
+ resolved "https://registry.yarnpkg.com/@redocly/reference-docs/-/reference-docs-2.26.3.tgz#a94b9afbcf6c7b859440fa3cf4a3a3ee6dcff572"
+ integrity sha512-eCJGB4yV2SyPbRavQi0x05/m2M+Znp7zNBIESoFHtKDrCSqzattuHG3SSTRdlw49yV7P/QsnfGm+9oIaFTdxdQ==
dependencies:
"@redocly/openapi-core" "1.0.0-beta.97"
"@redocly/vscode-json-languageservice" "3.4.9"
"@wojtekmaj/enzyme-adapter-react-17" "^0.6.2"
- classnames "^2.2.6"
codemirror "^5.65.0"
decko "^1.2.0"
deepmerge "^4.2.2"
@@ -1926,7 +1925,6 @@
perfect-scrollbar "^1.5.5"
polished "^3.6.5"
prismjs "^1.22.0"
- prop-types "^15.7.2"
query-string "^6.13.6"
react-codemirror2-react-17 "1.0.0"
react-tabs "^3.1.1"
@@ -4189,7 +4187,7 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
-classnames@^2.1.2, classnames@^2.2.6:
+classnames@^2.1.2:
version "2.3.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==