diff --git a/examples/landing/components/editor/Viewport/Sidebar/index.tsx b/examples/landing/components/editor/Viewport/Sidebar/index.tsx
index 325556b15..7677e376f 100644
--- a/examples/landing/components/editor/Viewport/Sidebar/index.tsx
+++ b/examples/landing/components/editor/Viewport/Sidebar/index.tsx
@@ -16,6 +16,86 @@ export const SidebarDiv = styled.div<{ enabled: boolean }>`
margin-right: ${(props) => (props.enabled ? 0 : -280)}px;
`;
+const CarbonAdsContainer = styled.div`
+ width: 100%;
+ margin-top: auto;
+
+ #carbonads * {
+ margin: initial;
+ padding: initial;
+ }
+
+ #carbonads {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', Helvetica, Arial,
+ sans-serif;
+
+ padding: 10px 0.5rem;
+ border-top: 1px solid rgb(0 0 0 / 6%);
+ }
+
+ #carbonads {
+ display: flex;
+ width: 100%;
+ background-color: transparent;
+ z-index: 100;
+ }
+
+ #carbonads a {
+ color: inherit;
+ text-decoration: none;
+ }
+
+ #carbonads a:hover {
+ color: inherit;
+ }
+
+ #carbonads span {
+ position: relative;
+ display: block;
+ overflow: hidden;
+ }
+
+ #carbonads .carbon-wrap {
+ display: flex;
+ }
+
+ #carbonads .carbon-img {
+ display: block;
+ margin: 0;
+ line-height: 1;
+ }
+
+ #carbonads .carbon-img img {
+ display: block;
+ }
+
+ #carbonads .carbon-text {
+ font-size: 11px;
+ padding: 10px;
+ margin-bottom: 16px;
+ line-height: 1.5;
+ text-align: left;
+ color: #333333;
+ font-weight: 400;
+ }
+
+ #carbonads .carbon-poweredby {
+ display: block;
+ padding: 6px 8px;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ font-weight: 600;
+ font-size: 8px;
+ line-height: 1;
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ color: #8f8f8f;
+ }
+`;
+
export const Sidebar = () => {
const [layersVisible, setLayerVisible] = useState(true);
const [toolbarVisible, setToolbarVisible] = useState(true);
@@ -46,6 +126,14 @@ export const Sidebar = () => {
+
+
+
);
diff --git a/examples/landing/styles/app.css b/examples/landing/styles/app.css
index d6751eb24..982abed39 100644
--- a/examples/landing/styles/app.css
+++ b/examples/landing/styles/app.css
@@ -25,3 +25,17 @@ body {
.transition {
transition: 0.4s cubic-bezier(0.19, 1, 0.22, 1);
}
+
+#carbonads * {
+ margin: initial;
+ padding: initial;
+}
+
+#carbonads {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', Helvetica, Arial,
+ sans-serif;
+
+ padding: 10px 0.5rem;
+ border-top: 1px solid rgb(0 0 0 / 6%);
+}
diff --git a/site/src/css/custom.css b/site/src/css/custom.css
index cf832302a..c7d04a538 100755
--- a/site/src/css/custom.css
+++ b/site/src/css/custom.css
@@ -323,6 +323,10 @@ html[data-theme='dark'] .api-description {
outline: none;
}
+.footer {
+ z-index: 98;
+}
+
.footer .footer__links {
margin-bottom: 1.5rem;
padding-bottom: 1.5rem;
@@ -450,3 +454,98 @@ html[data-theme='dark'] .api-description {
top: 0;
margin-top: 0;
}
+
+#carbonads-container {
+ padding: var(--ifm-menu-link-padding-vertical)
+ calc(var(--ifm-menu-link-padding-horizontal))
+ var(--ifm-menu-link-padding-vertical)
+ var(--ifm-menu-link-padding-horizontal);
+ border-top: 1px solid var(--ifm-toc-border-color);
+ border-right: 1px solid var(--ifm-toc-border-color);
+}
+
+@media screen and (max-width: 996px) {
+ #carbonads-container {
+ position: fixed;
+ margin-top: 20px;
+ width: 300px;
+ bottom: 5px;
+ z-index: 9;
+ left: 19px;
+ border-radius: 3px;
+ background: #fff;
+ box-shadow: 0px 3px 28px 0px rgb(1 1 1 / 11%);
+ border: none;
+ }
+}
+
+#carbonads * {
+ margin: initial;
+ padding: initial;
+}
+#carbonads {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', Helvetica, Arial,
+ sans-serif;
+}
+
+#carbonads {
+ display: flex;
+ max-width: 330px;
+ background-color: transparent;
+ z-index: 100;
+}
+
+#carbonads a {
+ color: inherit;
+ text-decoration: none;
+}
+
+#carbonads a:hover {
+ color: inherit;
+}
+
+#carbonads span {
+ position: relative;
+ display: block;
+ overflow: hidden;
+}
+
+#carbonads .carbon-wrap {
+ display: flex;
+}
+
+#carbonads .carbon-img {
+ display: block;
+ margin: 0;
+ line-height: 1;
+}
+
+#carbonads .carbon-img img {
+ display: block;
+}
+
+#carbonads .carbon-text {
+ font-size: 11px;
+ padding: 10px;
+ margin-bottom: 16px;
+ line-height: 1.5;
+ text-align: left;
+ color: #333333;
+ font-weight: 400;
+}
+
+#carbonads .carbon-poweredby {
+ display: block;
+ padding: 6px 8px;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ font-weight: 600;
+ font-size: 8px;
+ line-height: 1;
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ color: #8f8f8f;
+}
diff --git a/site/src/theme/DocSidebar/index.js b/site/src/theme/DocSidebar/index.js
new file mode 100644
index 000000000..fc49d8b2f
--- /dev/null
+++ b/site/src/theme/DocSidebar/index.js
@@ -0,0 +1,348 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import React, {useState, useCallback, useEffect, useRef, memo} from 'react';
+import clsx from 'clsx';
+import {useThemeConfig, isSamePath} from '@docusaurus/theme-common';
+import useUserPreferencesContext from '@theme/hooks/useUserPreferencesContext';
+import useLockBodyScroll from '@theme/hooks/useLockBodyScroll';
+import useWindowSize, {windowSizes} from '@theme/hooks/useWindowSize';
+import useScrollPosition from '@theme/hooks/useScrollPosition';
+import Link from '@docusaurus/Link';
+import isInternalUrl from '@docusaurus/isInternalUrl';
+import Logo from '@theme/Logo';
+import IconArrow from '@theme/IconArrow';
+import IconMenu from '@theme/IconMenu';
+import {translate} from '@docusaurus/Translate';
+import styles from './styles.module.css';
+const MOBILE_TOGGLE_SIZE = 24;
+
+function usePrevious(value) {
+ const ref = useRef(value);
+ useEffect(() => {
+ ref.current = value;
+ }, [value]);
+ return ref.current;
+}
+
+const isActiveSidebarItem = (item, activePath) => {
+ if (item.type === 'link') {
+ return isSamePath(item.href, activePath);
+ }
+
+ if (item.type === 'category') {
+ return item.items.some((subItem) =>
+ isActiveSidebarItem(subItem, activePath),
+ );
+ }
+
+ return false;
+}; // Optimize sidebar at each "level"
+// TODO this item should probably not receive the "activePath" props
+// TODO this triggers whole sidebar re-renders on navigation
+
+const DocSidebarItems = memo(function DocSidebarItems({items, ...props}) {
+ return items.map((item, index) => (
+
+ ));
+});
+
+function DocSidebarItem(props) {
+ switch (props.item.type) {
+ case 'category':
+ return ;
+
+ case 'link':
+ default:
+ return ;
+ }
+}
+
+function DocSidebarItemCategory({
+ item,
+ onItemClick,
+ collapsible,
+ activePath,
+ ...props
+}) {
+ const {items, label} = item;
+ const isActive = isActiveSidebarItem(item, activePath);
+ const wasActive = usePrevious(isActive); // active categories are always initialized as expanded
+ // the default (item.collapsed) is only used for non-active categories
+
+ const [collapsed, setCollapsed] = useState(() => {
+ if (!collapsible) {
+ return false;
+ }
+
+ return isActive ? false : item.collapsed;
+ });
+ const menuListRef = useRef(null);
+ const [menuListHeight, setMenuListHeight] = useState(undefined);
+
+ const handleMenuListHeight = (calc = true) => {
+ setMenuListHeight(
+ calc ? `${menuListRef.current?.scrollHeight}px` : undefined,
+ );
+ }; // If we navigate to a category, it should automatically expand itself
+
+ useEffect(() => {
+ const justBecameActive = isActive && !wasActive;
+
+ if (justBecameActive && collapsed) {
+ setCollapsed(false);
+ }
+ }, [isActive, wasActive, collapsed]);
+ const handleItemClick = useCallback(
+ (e) => {
+ e.preventDefault();
+
+ if (!menuListHeight) {
+ handleMenuListHeight();
+ }
+
+ setTimeout(() => setCollapsed((state) => !state), 100);
+ },
+ [menuListHeight],
+ );
+
+ if (items.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+ {label}
+
+ {
+ if (!collapsed) {
+ handleMenuListHeight(false);
+ }
+ }}>
+
+
+
+ );
+}
+
+function DocSidebarItemLink({
+ item,
+ onItemClick,
+ activePath,
+ collapsible: _collapsible,
+ ...props
+}) {
+ const {href, label} = item;
+ const isActive = isActiveSidebarItem(item, activePath);
+ return (
+
+
+ {label}
+
+
+ );
+}
+
+function useShowAnnouncementBar() {
+ const {isAnnouncementBarClosed} = useUserPreferencesContext();
+ const [showAnnouncementBar, setShowAnnouncementBar] = useState(
+ !isAnnouncementBarClosed,
+ );
+ useScrollPosition(({scrollY}) => {
+ if (!isAnnouncementBarClosed) {
+ setShowAnnouncementBar(scrollY === 0);
+ }
+ });
+ return showAnnouncementBar;
+}
+
+function useResponsiveSidebar() {
+ const [showResponsiveSidebar, setShowResponsiveSidebar] = useState(false);
+ useLockBodyScroll(showResponsiveSidebar);
+ const windowSize = useWindowSize();
+ useEffect(() => {
+ if (windowSize === windowSizes.desktop) {
+ setShowResponsiveSidebar(false);
+ }
+ }, [windowSize]);
+ const closeResponsiveSidebar = useCallback(
+ (e) => {
+ e.target.blur();
+ setShowResponsiveSidebar(false);
+ },
+ [setShowResponsiveSidebar],
+ );
+ const toggleResponsiveSidebar = useCallback(() => {
+ setShowResponsiveSidebar((value) => !value);
+ }, [setShowResponsiveSidebar]);
+ return {
+ showResponsiveSidebar,
+ closeResponsiveSidebar,
+ toggleResponsiveSidebar,
+ };
+}
+
+function HideableSidebarButton({onClick}) {
+ return (
+
+ );
+}
+
+function ResponsiveSidebarButton({responsiveSidebarOpened, onClick}) {
+ return (
+
+ );
+}
+
+function DocSidebar({
+ path,
+ sidebar,
+ sidebarCollapsible = true,
+ onCollapse,
+ isHidden,
+}) {
+ const showAnnouncementBar = useShowAnnouncementBar();
+ const {
+ navbar: {hideOnScroll},
+ hideableSidebar,
+ } = useThemeConfig();
+ const {isAnnouncementBarClosed} = useUserPreferencesContext();
+ const {
+ showResponsiveSidebar,
+ closeResponsiveSidebar,
+ toggleResponsiveSidebar,
+ } = useResponsiveSidebar();
+ return (
+
+ {hideOnScroll &&
}
+
+
+
+
+ {hideableSidebar &&
}
+
+ );
+}
+
+export default DocSidebar;
diff --git a/site/src/theme/DocSidebar/styles.module.css b/site/src/theme/DocSidebar/styles.module.css
new file mode 100644
index 000000000..c9508e40e
--- /dev/null
+++ b/site/src/theme/DocSidebar/styles.module.css
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+:root {
+ --collapse-button-bg-color-dark: #2e333a;
+}
+
+@media (min-width: 997px) {
+ .sidebar {
+ display: flex;
+ flex-direction: column;
+ max-height: 100vh;
+ height: 100%;
+ position: sticky;
+ top: 0;
+ padding-top: var(--ifm-navbar-height);
+ width: var(--doc-sidebar-width);
+ transition: opacity 50ms ease;
+ }
+
+ .sidebarWithHideableNavbar {
+ padding-top: 0;
+ }
+
+ .sidebarHidden {
+ opacity: 0;
+ height: 0;
+ overflow: hidden;
+ visibility: hidden;
+ }
+
+ .sidebarLogo {
+ display: flex !important;
+ align-items: center;
+ margin: 0 var(--ifm-navbar-padding-horizontal);
+ min-height: var(--ifm-navbar-height);
+ max-height: var(--ifm-navbar-height);
+ color: inherit !important;
+ text-decoration: none !important;
+ }
+
+ .sidebarLogo img {
+ margin-right: 0.5rem;
+ height: 2rem;
+ }
+
+ .menu {
+ flex-grow: 1;
+ padding: 0.5rem;
+ }
+
+ .menuLinkText {
+ cursor: initial;
+ }
+
+ .menuLinkText:hover {
+ background: none;
+ }
+
+ .menuWithAnnouncementBar {
+ margin-bottom: var(--docusaurus-announcement-bar-height);
+ }
+
+ .collapseSidebarButton {
+ display: block !important;
+ background-color: var(--ifm-button-background-color);
+ height: 40px;
+ position: sticky;
+ bottom: 0;
+ border-radius: 0;
+ border: 1px solid var(--ifm-toc-border-color);
+ }
+
+ .collapseSidebarButtonIcon {
+ transform: rotate(180deg);
+ margin-top: 4px;
+ }
+ html[dir='rtl'] .collapseSidebarButtonIcon {
+ transform: rotate(0);
+ }
+
+ html[data-theme='dark'] .collapseSidebarButton {
+ background-color: var(--collapse-button-bg-color-dark);
+ }
+
+ html[data-theme='dark'] .collapseSidebarButton:hover,
+ html[data-theme='dark'] .collapseSidebarButton:focus {
+ background-color: var(--ifm-color-emphasis-200);
+ }
+}
+
+.sidebarLogo,
+.collapseSidebarButton {
+ display: none;
+}
+
+.sidebarMenuIcon {
+ vertical-align: middle;
+}
+
+.sidebarMenuCloseIcon {
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ height: 24px;
+ font-size: 1.5rem;
+ font-weight: var(--ifm-font-weight-bold);
+ line-height: 0.9;
+ width: 24px;
+}
+
+:global(.menu__list) :global(.menu__list) {
+ overflow-y: hidden;
+ will-change: height;
+ transition: height var(--ifm-transition-fast) linear;
+}
+
+:global(.menu__list-item--collapsed) :global(.menu__list) {
+ height: 0 !important;
+}
+
+.menuLinkExternal {
+ align-items: center;
+}
+.menuLinkExternal:after {
+ content: '';
+ height: 1.15rem;
+ width: 1.15rem;
+ min-width: 1.15rem;
+ margin: 0 0 0 3%;
+ background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z'/%3E%3C/svg%3E")
+ no-repeat;
+ filter: var(--ifm-menu-link-sublist-icon-filter);
+}