From b0e8621d2ef1a480be504bea33cef1f2500bda5d Mon Sep 17 00:00:00 2001 From: Rob Dominguez Date: Sat, 7 Jun 2025 13:38:00 -0700 Subject: [PATCH 1/5] Add POC for sidebar --- .gitignore | 1 + docs/billing/_category_.json | 0 src/theme/DocSidebar/CustomSidebar.css | 193 ++++++++++++++++++ .../Desktop/CollapseButton/index.tsx | 31 +++ .../Desktop/CollapseButton/styles.module.css | 40 ++++ .../DocSidebar/Desktop/Content/index.tsx | 54 +++++ .../Desktop/Content/styles.module.css | 16 ++ src/theme/DocSidebar/Desktop/index.tsx | 33 +++ .../DocSidebar/Desktop/styles.module.css | 37 ++++ src/theme/DocSidebar/Mobile/index.tsx | 47 +++++ src/theme/DocSidebar/categories.ts | 32 +++ src/theme/DocSidebar/index.tsx | 90 ++++++++ src/theme/DocSidebar/utils.ts | 124 +++++++++++ 13 files changed, 698 insertions(+) delete mode 100644 docs/billing/_category_.json create mode 100644 src/theme/DocSidebar/CustomSidebar.css create mode 100644 src/theme/DocSidebar/Desktop/CollapseButton/index.tsx create mode 100644 src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css create mode 100644 src/theme/DocSidebar/Desktop/Content/index.tsx create mode 100644 src/theme/DocSidebar/Desktop/Content/styles.module.css create mode 100644 src/theme/DocSidebar/Desktop/index.tsx create mode 100644 src/theme/DocSidebar/Desktop/styles.module.css create mode 100644 src/theme/DocSidebar/Mobile/index.tsx create mode 100644 src/theme/DocSidebar/categories.ts create mode 100644 src/theme/DocSidebar/index.tsx create mode 100644 src/theme/DocSidebar/utils.ts diff --git a/.gitignore b/.gitignore index 19c3015d..5332d144 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ node_modules/ .env.test.local .env.production.local .secrets +notes.txt .direnv diff --git a/docs/billing/_category_.json b/docs/billing/_category_.json deleted file mode 100644 index e69de29b..00000000 diff --git a/src/theme/DocSidebar/CustomSidebar.css b/src/theme/DocSidebar/CustomSidebar.css new file mode 100644 index 00000000..1c26428d --- /dev/null +++ b/src/theme/DocSidebar/CustomSidebar.css @@ -0,0 +1,193 @@ +.custom-sidebar { + height: 100vh; + background: var(--sidebar-bg-color); + width: 280px; + overflow-y: auto; + padding-top: 5rem; +} + +.custom-sidebar__content { + padding: 1.5rem 0; +} + +.custom-sidebar__category { + margin-bottom: 2rem; +} + +.custom-sidebar__category:last-child { + margin-bottom: 1rem; +} + +.custom-sidebar__category-title { + margin: 0 0 0.75rem 0; + padding: 0 1.5rem; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #6b7280; +} + +.custom-sidebar__category-content { + display: flex; + flex-direction: column; +} + +.custom-sidebar__empty { + color: #9ca3af; + font-style: italic; + margin: 0; + font-size: 0.875rem; + padding: 0 1.5rem; +} + +.custom-sidebar__items { + display: flex; + flex-direction: column; +} + +.custom-sidebar__item { + margin: 0; +} + +.custom-sidebar__link { + display: flex; + align-items: center; + padding: 0.5rem 1.5rem; + color: #374151; + text-decoration: none; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; + transition: all 0.15s ease; + border-left: 3px solid transparent; +} + + +.custom-sidebar__link--active { + background-color: #eff6ff; + color: #2563eb; + border-left-color: #2563eb; + font-weight: 600; +} + +.custom-sidebar__collapsible { + margin: 0; +} + +.custom-sidebar__collapsible[open] .custom-sidebar__folder-title { + color: #111827; + background-color: #f9fafb; +} + +.custom-sidebar__folder-title { + display: flex; + align-items: center; + padding: 0.5rem 1.5rem; + cursor: pointer; + font-weight: 500; + font-size: 0.875rem; + color: #374151; + transition: all 0.15s ease; + border: none; + background: none; + width: 100%; + text-align: left; + border-left: 3px solid transparent; + list-style: none; +} + +.custom-sidebar__folder-title::-webkit-details-marker { + display: none; +} + +.custom-sidebar__folder-title::before { + content: ''; + width: 0; + height: 0; + border-left: 4px solid #9ca3af; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + margin-right: 0.5rem; + transition: transform 0.15s ease; +} + +.custom-sidebar__collapsible[open] .custom-sidebar__folder-title::before { + transform: rotate(90deg); +} + + +.custom-sidebar__subitems { + list-style: none; + padding: 0; + margin: 0; + background-color: #fafafa; + border-left: 3px solid #f3f4f6; +} + +.custom-sidebar__subitems .custom-sidebar__link { + padding-left: 2.5rem; + font-weight: 400; + color: #6b7280; + border-left: none; +} + + +.custom-sidebar__subitems .custom-sidebar__link--active { + background-color: #dbeafe; + color: #1d4ed8; + font-weight: 500; +} + +/* Scrollbar styling */ +.custom-sidebar::-webkit-scrollbar { + width: 6px; +} + +.custom-sidebar::-webkit-scrollbar-track { + background: transparent; +} + +.custom-sidebar::-webkit-scrollbar-thumb { + background: #d1d5db; + border-radius: 3px; +} + + +/* Dark mode support (optional) */ +@media (prefers-color-scheme: dark) { + .custom-sidebar { + background: #111827; + border-right-color: #374151; + } + + .custom-sidebar__category-title { + color: #9ca3af; + } + + .custom-sidebar__link { + color: #d1d5db; + } + + + .custom-sidebar__link--active { + background-color: #1e3a8a; + color: #60a5fa; + border-left-color: #3b82f6; + } + + .custom-sidebar__folder-title { + color: #d1d5db; + } + + + .custom-sidebar__subitems { + background-color: #0f172a; + border-left-color: #374151; + } + + .custom-sidebar__subitems .custom-sidebar__link { + color: #9ca3af; + } + +} diff --git a/src/theme/DocSidebar/Desktop/CollapseButton/index.tsx b/src/theme/DocSidebar/Desktop/CollapseButton/index.tsx new file mode 100644 index 00000000..d2c5b662 --- /dev/null +++ b/src/theme/DocSidebar/Desktop/CollapseButton/index.tsx @@ -0,0 +1,31 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import {translate} from '@docusaurus/Translate'; +import IconArrow from '@theme/Icon/Arrow'; +import type {Props} from '@theme/DocSidebar/Desktop/CollapseButton'; + +import styles from './styles.module.css'; + +export default function CollapseButton({onClick}: Props): ReactNode { + return ( + + ); +} diff --git a/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css b/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css new file mode 100644 index 00000000..df46519f --- /dev/null +++ b/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css @@ -0,0 +1,40 @@ +:root { + --docusaurus-collapse-button-bg: transparent; + --docusaurus-collapse-button-bg-hover: rgb(0 0 0 / 10%); +} + +[data-theme='dark']:root { + --docusaurus-collapse-button-bg: rgb(255 255 255 / 5%); + --docusaurus-collapse-button-bg-hover: rgb(255 255 255 / 10%); +} + +@media (min-width: 997px) { + .collapseSidebarButton { + display: block !important; + background-color: var(--docusaurus-collapse-button-bg); + height: 40px; + position: sticky; + bottom: 0; + border-radius: 0; + border: 1px solid var(--ifm-toc-border-color); + } + + .collapseSidebarButtonIcon { + transform: rotate(180deg); + margin-top: 4px; + } + + [dir='rtl'] .collapseSidebarButtonIcon { + transform: rotate(0); + } + + .collapseSidebarButton:hover, + .collapseSidebarButton:focus { + background-color: var(--docusaurus-collapse-button-bg-hover); + } +} + +.collapseSidebarButton { + display: none; + margin: 0; +} diff --git a/src/theme/DocSidebar/Desktop/Content/index.tsx b/src/theme/DocSidebar/Desktop/Content/index.tsx new file mode 100644 index 00000000..fbd058c3 --- /dev/null +++ b/src/theme/DocSidebar/Desktop/Content/index.tsx @@ -0,0 +1,54 @@ +import React, {type ReactNode, useState} from 'react'; +import clsx from 'clsx'; +import {ThemeClassNames} from '@docusaurus/theme-common'; +import { + useAnnouncementBar, + useScrollPosition, +} from '@docusaurus/theme-common/internal'; +import {translate} from '@docusaurus/Translate'; +import DocSidebarItems from '@theme/DocSidebarItems'; +import type {Props} from '@theme/DocSidebar/Desktop/Content'; + +import styles from './styles.module.css'; + +function useShowAnnouncementBar() { + const {isActive} = useAnnouncementBar(); + const [showAnnouncementBar, setShowAnnouncementBar] = useState(isActive); + + useScrollPosition( + ({scrollY}) => { + if (isActive) { + setShowAnnouncementBar(scrollY === 0); + } + }, + [isActive], + ); + return isActive && showAnnouncementBar; +} + +export default function DocSidebarDesktopContent({ + path, + sidebar, + className, +}: Props): ReactNode { + const showAnnouncementBar = useShowAnnouncementBar(); + + return ( + + ); +} diff --git a/src/theme/DocSidebar/Desktop/Content/styles.module.css b/src/theme/DocSidebar/Desktop/Content/styles.module.css new file mode 100644 index 00000000..0c43a4e4 --- /dev/null +++ b/src/theme/DocSidebar/Desktop/Content/styles.module.css @@ -0,0 +1,16 @@ +@media (min-width: 997px) { + .menu { + flex-grow: 1; + padding: 0.5rem; + } + @supports (scrollbar-gutter: stable) { + .menu { + padding: 0.5rem 0 0.5rem 0.5rem; + scrollbar-gutter: stable; + } + } + + .menuWithAnnouncementBar { + margin-bottom: var(--docusaurus-announcement-bar-height); + } +} diff --git a/src/theme/DocSidebar/Desktop/index.tsx b/src/theme/DocSidebar/Desktop/index.tsx new file mode 100644 index 00000000..9446ba23 --- /dev/null +++ b/src/theme/DocSidebar/Desktop/index.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import clsx from 'clsx'; +import {useThemeConfig} from '@docusaurus/theme-common'; +import Logo from '@theme/Logo'; +import CollapseButton from '@theme/DocSidebar/Desktop/CollapseButton'; +import Content from '@theme/DocSidebar/Desktop/Content'; +import type {Props} from '@theme/DocSidebar/Desktop'; + +import styles from './styles.module.css'; + +function DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) { + const { + navbar: {hideOnScroll}, + docs: { + sidebar: {hideable}, + }, + } = useThemeConfig(); + + return ( +
+ {hideOnScroll && } + + {hideable && } +
+ ); +} + +export default React.memo(DocSidebarDesktop); diff --git a/src/theme/DocSidebar/Desktop/styles.module.css b/src/theme/DocSidebar/Desktop/styles.module.css new file mode 100644 index 00000000..c5d5e50e --- /dev/null +++ b/src/theme/DocSidebar/Desktop/styles.module.css @@ -0,0 +1,37 @@ +@media (min-width: 997px) { + .sidebar { + display: flex; + flex-direction: column; + height: 100%; + padding-top: var(--ifm-navbar-height); + width: var(--doc-sidebar-width); + } + + .sidebarWithHideableNavbar { + padding-top: 0; + } + + .sidebarHidden { + opacity: 0; + 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; + } +} + +.sidebarLogo { + display: none; +} diff --git a/src/theme/DocSidebar/Mobile/index.tsx b/src/theme/DocSidebar/Mobile/index.tsx new file mode 100644 index 00000000..c4b33172 --- /dev/null +++ b/src/theme/DocSidebar/Mobile/index.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import clsx from 'clsx'; +import { + NavbarSecondaryMenuFiller, + type NavbarSecondaryMenuComponent, + ThemeClassNames, +} from '@docusaurus/theme-common'; +import {useNavbarMobileSidebar} from '@docusaurus/theme-common/internal'; +import DocSidebarItems from '@theme/DocSidebarItems'; +import type {Props} from '@theme/DocSidebar/Mobile'; + +// eslint-disable-next-line react/function-component-definition +const DocSidebarMobileSecondaryMenu: NavbarSecondaryMenuComponent = ({ + sidebar, + path, +}) => { + const mobileSidebar = useNavbarMobileSidebar(); + return ( + + ); +}; + +function DocSidebarMobile(props: Props) { + return ( + + ); +} + +export default React.memo(DocSidebarMobile); diff --git a/src/theme/DocSidebar/categories.ts b/src/theme/DocSidebar/categories.ts new file mode 100644 index 00000000..a748bb5a --- /dev/null +++ b/src/theme/DocSidebar/categories.ts @@ -0,0 +1,32 @@ +export const CATEGORY_CONFIG = { + gettingStarted: { + title: 'Getting Started', + directories: ['quickstart', 'project-configuration'], + exactMatch: true, + }, + coreConcepts: { + title: 'Core Concepts', + directories: ['data-modeling', 'data-sources', 'business-logic'], + exactMatch: false, + }, + buildingApps: { + title: 'Building Apps', + directories: ['how-to-build-with-promptql', 'promptql-apis', 'promptql-playground'], + exactMatch: true, + }, + deployment: { + title: 'Deployment & Operations', + directories: ['deployment', 'observability', 'private-ddn'], + exactMatch: true, + }, + guides: { + title: 'Guides & Recipes', + directories: ['recipes'], + exactMatch: true, + }, + reference: { + title: 'Reference', + directories: ['reference', 'billing', 'help'], + exactMatch: true, + }, +} as const; diff --git a/src/theme/DocSidebar/index.tsx b/src/theme/DocSidebar/index.tsx new file mode 100644 index 00000000..17c9baf3 --- /dev/null +++ b/src/theme/DocSidebar/index.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { useLocation } from '@docusaurus/router'; +import type { PropSidebarItem } from '@docusaurus/plugin-content-docs'; +import { + buildCategories, + isActiveLink, + hasActiveChild, + type Category +} from './utils'; +import './CustomSidebar.css'; + +interface CustomSidebarProps { + sidebar: PropSidebarItem[]; +} + +const CustomSidebar: React.FC = ({ sidebar }) => { + const location = useLocation(); + const categories: Category[] = buildCategories(sidebar); + + const renderSidebarItem = (item: PropSidebarItem): React.ReactElement => { + if (item.type === 'link') { + const href = (item as any).href || ''; + const isActive = isActiveLink(href, location.pathname); + + return ( + + {(item as any).label || 'Untitled'} + + ); + } + + if (item.type === 'category') { + const items = (item as any).items; + if (items && Array.isArray(items)) { + const shouldExpand = hasActiveChild(items, location.pathname); + + return ( +
+ + {(item as any).label || 'Untitled Category'} + +
    + {items.map((subItem: PropSidebarItem, subIndex: number) => ( +
  • + {renderSidebarItem(subItem)} +
  • + ))} +
+
+ ); + } + } + + return {(item as any).label || 'Unknown item'}; + }; + + const renderCategory = (category: Category) => ( +
+

+ {category.title} +

+
+ {category.items.length === 0 ? ( +

No items yet

+ ) : ( +
+ {category.items.map((item, index) => ( +
+ {renderSidebarItem(item)} +
+ ))} +
+ )} +
+
+ ); + + return ( +
+
+ {categories.map(renderCategory)} +
+
+ ); +}; + +export default CustomSidebar; diff --git a/src/theme/DocSidebar/utils.ts b/src/theme/DocSidebar/utils.ts new file mode 100644 index 00000000..a22b7266 --- /dev/null +++ b/src/theme/DocSidebar/utils.ts @@ -0,0 +1,124 @@ +import type { PropSidebarItem } from '@docusaurus/plugin-content-docs'; +import { CATEGORY_CONFIG } from './categories'; + +export interface Category { + id: string; + title: string; + items: PropSidebarItem[]; +} + +/** + * Extract directory name from a Docusaurus path + */ +export const getDirectoryFromPath = (path: string): string => { + const cleanPath = path.replace(/^\/+|\/+$/g, ''); + const segments = cleanPath.split('/'); + + if (segments[0] === 'docs' && segments.length > 1) { + return segments[1]; + } + return segments[0] || ''; +}; + +/** + * Check if a sidebar item matches any of the given patterns + */ +export const itemMatchesPattern = ( + item: PropSidebarItem, + patterns: readonly string[], + exactMatch: boolean = false +): boolean => { + return patterns.some(pattern => { + if (item.type === 'link') { + const href = (item as any).href; + if (href) { + const directory = getDirectoryFromPath(href); + console.log(`Checking link: ${(item as any).label || 'No label'} -> ${href} -> directory: ${directory} against pattern: ${pattern}`); + if (exactMatch) { + return directory === pattern; + } else { + return directory.includes(pattern); + } + } + } + + if (item.type === 'category') { + console.log(`Checking category: ${(item as any).label || 'No label'}, has ${(item as any).items?.length || 0} items`); + + const href = (item as any).href; + if (href) { + const directory = getDirectoryFromPath(href); + console.log(`Category href: ${href} -> directory: ${directory} against pattern: ${pattern}`); + if (exactMatch) { + if (directory === pattern) return true; + } else { + if (directory.includes(pattern)) return true; + } + } + + const items = (item as any).items; + if (items && Array.isArray(items)) { + const hasMatchingChild = items.some((subItem: any) => { + if (subItem.type === 'link' && subItem.href) { + const directory = getDirectoryFromPath(subItem.href); + if (exactMatch) { + return directory === pattern; + } else { + return directory.includes(pattern); + } + } + return false; + }); + if (hasMatchingChild) { + return true; + } + } + } + + return false; + }); +}; + +/** + * Filter sidebar items that match the given category configuration + */ +export const getItemsForCategory = ( + sidebar: PropSidebarItem[], + categoryConfig: readonly string[], + exactMatch: boolean = false +): PropSidebarItem[] => { + if (!sidebar || !Array.isArray(sidebar)) return []; + + const matchedItems = sidebar.filter((item: PropSidebarItem) => { + return itemMatchesPattern(item, categoryConfig, exactMatch); + }); + + return matchedItems; +}; + +/** + * Build categories from the configuration + */ +export const buildCategories = (sidebar: PropSidebarItem[]): Category[] => { + return Object.entries(CATEGORY_CONFIG).map(([id, config]) => ({ + id, + title: config.title, + items: getItemsForCategory(sidebar, config.directories, config.exactMatch), + })); +}; + +/** + * Check if a link is currently active based on the current pathname + */ +export const isActiveLink = (href: string, currentPathname: string): boolean => { + return currentPathname === href || currentPathname.startsWith(href + '/'); +}; + +/** + * Check if a category has any active child items + */ +export const hasActiveChild = (items: any[], currentPathname: string): boolean => { + return items.some((subItem: any) => + subItem.type === 'link' && subItem.href && isActiveLink(subItem.href, currentPathname) + ); +}; From f5989b7f6d1d5e582c53f5bc5e2bd24dab17cd60 Mon Sep 17 00:00:00 2001 From: Rob Dominguez Date: Fri, 27 Jun 2025 09:03:05 -0700 Subject: [PATCH 2/5] Add pages to getting-started section and make basic edits to styling Starting to tweak things here; for an MVP, we've agreed on some basic stylings after consultation with the studio folks. Additionally, we're beginning to move pages into the right categories. --- src/theme/DocRoot/Layout/Sidebar/styles.module.css | 3 ++- src/theme/DocSidebar/CustomSidebar.css | 2 +- src/theme/DocSidebar/categories.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/theme/DocRoot/Layout/Sidebar/styles.module.css b/src/theme/DocRoot/Layout/Sidebar/styles.module.css index 221aabf5..5d7d27e2 100644 --- a/src/theme/DocRoot/Layout/Sidebar/styles.module.css +++ b/src/theme/DocRoot/Layout/Sidebar/styles.module.css @@ -1,10 +1,11 @@ :root { - --doc-sidebar-width: 300px; + --doc-sidebar-width: 250px; --doc-sidebar-hidden-width: 30px; } .docSidebarContainer { display: none; + } @media (min-width: 997px) { diff --git a/src/theme/DocSidebar/CustomSidebar.css b/src/theme/DocSidebar/CustomSidebar.css index 1c26428d..91c5da3e 100644 --- a/src/theme/DocSidebar/CustomSidebar.css +++ b/src/theme/DocSidebar/CustomSidebar.css @@ -1,6 +1,6 @@ .custom-sidebar { height: 100vh; - background: var(--sidebar-bg-color); + background-color: var(--sidebar-bg-color) !important; width: 280px; overflow-y: auto; padding-top: 5rem; diff --git a/src/theme/DocSidebar/categories.ts b/src/theme/DocSidebar/categories.ts index a748bb5a..71c43f8b 100644 --- a/src/theme/DocSidebar/categories.ts +++ b/src/theme/DocSidebar/categories.ts @@ -1,7 +1,7 @@ export const CATEGORY_CONFIG = { gettingStarted: { title: 'Getting Started', - directories: ['quickstart', 'project-configuration'], + directories: ['quickstart', 'how-to-talk-to-promptql', 'capabilities', 'decision-making', 'automation'], exactMatch: true, }, coreConcepts: { From a5ab673fb6c51b4f9ff7db0fc6015cb874d74591 Mon Sep 17 00:00:00 2001 From: Rob Dominguez Date: Fri, 27 Jun 2025 12:49:39 -0700 Subject: [PATCH 3/5] Sort CSS Sorts both light and dark modes to have a consistent feel. I'd like to spend some time at the start of next week dialing in the state changes for expanding/collapsing nested categories in the sidebar. --- src/css/custom.css | 4 + .../DocRoot/Layout/Sidebar/styles.module.css | 9 ++ src/theme/DocSidebar/CustomSidebar.css | 82 +++++-------------- src/theme/DocSidebar/index.tsx | 29 +++---- src/theme/DocSidebar/utils.ts | 19 +++-- 5 files changed, 59 insertions(+), 84 deletions(-) diff --git a/src/css/custom.css b/src/css/custom.css index b3e29341..4febd77a 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -25,6 +25,8 @@ --main-bg-color: #ffffff; --sidebar-bg-color: #f9fafb; --sidebar-bg-color-hover: #f1f5f9; + --sidebar-bg-color-active: #e5e7eb; + --sidebar-margin-color-active: #9ca3af; --heading-color: #1f2937; --heading-color-hover: #0f172a; --sidebar-title-color: #374151; @@ -122,6 +124,8 @@ html[data-theme='dark'] { --main-bg-color: #111827; --sidebar-bg-color: #060e23; --sidebar-bg-color-hover: #1f2937; + --sidebar-bg-color-active: #142046; + --sidebar-margin-color-active: #1f5aff; --heading-color: #e5e7eb; --heading-color-hover: #e5e7eb; --sidebar-title-color: #d1d5db; diff --git a/src/theme/DocRoot/Layout/Sidebar/styles.module.css b/src/theme/DocRoot/Layout/Sidebar/styles.module.css index 5d7d27e2..76b1058d 100644 --- a/src/theme/DocRoot/Layout/Sidebar/styles.module.css +++ b/src/theme/DocRoot/Layout/Sidebar/styles.module.css @@ -8,6 +8,15 @@ } +.sidebarViewport { + top: 0; + position: sticky; + height: 100%; + max-height: 100vh; + width: 100%; + overflow-x: hidden; +} + @media (min-width: 997px) { .docSidebarContainer { display: block; diff --git a/src/theme/DocSidebar/CustomSidebar.css b/src/theme/DocSidebar/CustomSidebar.css index 91c5da3e..128558be 100644 --- a/src/theme/DocSidebar/CustomSidebar.css +++ b/src/theme/DocSidebar/CustomSidebar.css @@ -1,7 +1,7 @@ .custom-sidebar { height: 100vh; background-color: var(--sidebar-bg-color) !important; - width: 280px; + width: 100%; overflow-y: auto; padding-top: 5rem; } @@ -54,7 +54,7 @@ display: flex; align-items: center; padding: 0.5rem 1.5rem; - color: #374151; + color: var(--sidebar-title-color) !important; text-decoration: none; font-size: 0.875rem; line-height: 1.25rem; @@ -63,11 +63,13 @@ border-left: 3px solid transparent; } +.custom-sidebar__link:hover { + color: var(--body-link-color-hover) !important; +} .custom-sidebar__link--active { - background-color: #eff6ff; - color: #2563eb; - border-left-color: #2563eb; + background-color: var(--sidebar-bg-color-active); + border-left-color: var(--sidebar-margin-color-active); font-weight: 600; } @@ -75,19 +77,13 @@ margin: 0; } -.custom-sidebar__collapsible[open] .custom-sidebar__folder-title { - color: #111827; - background-color: #f9fafb; -} .custom-sidebar__folder-title { display: flex; align-items: center; padding: 0.5rem 1.5rem; cursor: pointer; - font-weight: 500; font-size: 0.875rem; - color: #374151; transition: all 0.15s ease; border: none; background: none; @@ -97,46 +93,38 @@ list-style: none; } -.custom-sidebar__folder-title::-webkit-details-marker { - display: none; +.custom-sidebar__subitems .custom-sidebar__folder-title { + padding-left: 40px; } -.custom-sidebar__folder-title::before { - content: ''; - width: 0; - height: 0; - border-left: 4px solid #9ca3af; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - margin-right: 0.5rem; - transition: transform 0.15s ease; + +.custom-sidebar__collapsible[open] .custom-sidebar__subitems .custom-sidebar__subitems .custom-sidebar__link { + padding-left: 60px !important; } -.custom-sidebar__collapsible[open] .custom-sidebar__folder-title::before { - transform: rotate(90deg); +.custom-sidebar__folder-title:hover { + color: var(--body-link-color-hover); } +.custom-sidebar__folder-title::-webkit-details-marker { + display: none; +} .custom-sidebar__subitems { list-style: none; padding: 0; margin: 0; - background-color: #fafafa; - border-left: 3px solid #f3f4f6; } .custom-sidebar__subitems .custom-sidebar__link { padding-left: 2.5rem; font-weight: 400; color: #6b7280; - border-left: none; + border-left: 3px solid transparent; } - .custom-sidebar__subitems .custom-sidebar__link--active { - background-color: #dbeafe; - color: #1d4ed8; - font-weight: 500; + border-left: 3px solid var(--sidebar-margin-color-active); } /* Scrollbar styling */ @@ -153,41 +141,15 @@ border-radius: 3px; } - /* Dark mode support (optional) */ @media (prefers-color-scheme: dark) { .custom-sidebar { background: #111827; border-right-color: #374151; } - - .custom-sidebar__category-title { - color: #9ca3af; - } - - .custom-sidebar__link { - color: #d1d5db; - } - - + .custom-sidebar__link--active { - background-color: #1e3a8a; - color: #60a5fa; - border-left-color: #3b82f6; - } - - .custom-sidebar__folder-title { - color: #d1d5db; - } - - - .custom-sidebar__subitems { - background-color: #0f172a; - border-left-color: #374151; - } - - .custom-sidebar__subitems .custom-sidebar__link { - color: #9ca3af; + background-color: var(--sidebar-bg-color-active); + border-left-color: var(--sidebar-margin-color-active); } - } diff --git a/src/theme/DocSidebar/index.tsx b/src/theme/DocSidebar/index.tsx index 17c9baf3..fd850496 100644 --- a/src/theme/DocSidebar/index.tsx +++ b/src/theme/DocSidebar/index.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { useLocation } from '@docusaurus/router'; import type { PropSidebarItem } from '@docusaurus/plugin-content-docs'; -import { - buildCategories, - isActiveLink, +import Link from '@docusaurus/Link'; +import { + buildCategories, + isActiveLink, hasActiveChild, - type Category + type Category, } from './utils'; import './CustomSidebar.css'; @@ -21,17 +22,17 @@ const CustomSidebar: React.FC = ({ sidebar }) => { if (item.type === 'link') { const href = (item as any).href || ''; const isActive = isActiveLink(href, location.pathname); - + return ( - {(item as any).label || 'Untitled'} - + ); } - + if (item.type === 'category') { const items = (item as any).items; if (items && Array.isArray(items)) { @@ -44,24 +45,20 @@ const CustomSidebar: React.FC = ({ sidebar }) => {
    {items.map((subItem: PropSidebarItem, subIndex: number) => ( -
  • - {renderSidebarItem(subItem)} -
  • +
  • {renderSidebarItem(subItem)}
  • ))}
); } } - + return {(item as any).label || 'Unknown item'}; }; const renderCategory = (category: Category) => (
-

- {category.title} -

+

{category.title}

{category.items.length === 0 ? (

No items yet

diff --git a/src/theme/DocSidebar/utils.ts b/src/theme/DocSidebar/utils.ts index a22b7266..78bd4651 100644 --- a/src/theme/DocSidebar/utils.ts +++ b/src/theme/DocSidebar/utils.ts @@ -33,7 +33,6 @@ export const itemMatchesPattern = ( const href = (item as any).href; if (href) { const directory = getDirectoryFromPath(href); - console.log(`Checking link: ${(item as any).label || 'No label'} -> ${href} -> directory: ${directory} against pattern: ${pattern}`); if (exactMatch) { return directory === pattern; } else { @@ -43,12 +42,9 @@ export const itemMatchesPattern = ( } if (item.type === 'category') { - console.log(`Checking category: ${(item as any).label || 'No label'}, has ${(item as any).items?.length || 0} items`); - const href = (item as any).href; if (href) { const directory = getDirectoryFromPath(href); - console.log(`Category href: ${href} -> directory: ${directory} against pattern: ${pattern}`); if (exactMatch) { if (directory === pattern) return true; } else { @@ -117,8 +113,15 @@ export const isActiveLink = (href: string, currentPathname: string): boolean => /** * Check if a category has any active child items */ -export const hasActiveChild = (items: any[], currentPathname: string): boolean => { - return items.some((subItem: any) => - subItem.type === 'link' && subItem.href && isActiveLink(subItem.href, currentPathname) - ); +export const hasActiveChild = (items: PropSidebarItem[], pathname: string): boolean => { + return items.some(item => { + if (item.type === 'link') { + const href = (item as any).href; + return pathname.startsWith(href); // deep match + } + if (item.type === 'category') { + return hasActiveChild((item as any).items || [], pathname); + } + return false; + }); }; From 6fdfbef609befa954e809c1244dab0f33810cbd3 Mon Sep 17 00:00:00 2001 From: Rob Dominguez Date: Fri, 27 Jun 2025 13:37:19 -0700 Subject: [PATCH 4/5] Add categories --- src/theme/DocSidebar/categories.ts | 14 +++-- src/theme/DocSidebar/utils.ts | 94 +++++++++++++++++++++++++++--- 2 files changed, 97 insertions(+), 11 deletions(-) diff --git a/src/theme/DocSidebar/categories.ts b/src/theme/DocSidebar/categories.ts index 71c43f8b..d8dce87f 100644 --- a/src/theme/DocSidebar/categories.ts +++ b/src/theme/DocSidebar/categories.ts @@ -1,4 +1,10 @@ -export const CATEGORY_CONFIG = { +type CategoryConfig = { + title: string; + directories: string[]; + exactMatch: boolean; +}; + +export const CATEGORY_CONFIG: Record = { gettingStarted: { title: 'Getting Started', directories: ['quickstart', 'how-to-talk-to-promptql', 'capabilities', 'decision-making', 'automation'], @@ -6,12 +12,12 @@ export const CATEGORY_CONFIG = { }, coreConcepts: { title: 'Core Concepts', - directories: ['data-modeling', 'data-sources', 'business-logic'], + directories: ['data-modeling', 'data-sources', 'business-logic', 'auth'], exactMatch: false, }, buildingApps: { title: 'Building Apps', - directories: ['how-to-build-with-promptql', 'promptql-apis', 'promptql-playground'], + directories: ['project-configuration', 'how-to-build-with-promptql', 'promptql-apis', 'promptql-playground'], exactMatch: true, }, deployment: { @@ -29,4 +35,4 @@ export const CATEGORY_CONFIG = { directories: ['reference', 'billing', 'help'], exactMatch: true, }, -} as const; +}; diff --git a/src/theme/DocSidebar/utils.ts b/src/theme/DocSidebar/utils.ts index 78bd4651..438090da 100644 --- a/src/theme/DocSidebar/utils.ts +++ b/src/theme/DocSidebar/utils.ts @@ -20,6 +20,81 @@ export const getDirectoryFromPath = (path: string): string => { return segments[0] || ''; }; +/** + * Get the primary directory for an item (handles nested categories) + */ +export const getPrimaryDirectory = (item: PropSidebarItem): string => { + if (item.type === 'link') { + const href = (item as any).href; + if (href) { + return getDirectoryFromPath(href); + } + } + + if (item.type === 'category') { + // Check if the category itself has an href + const href = (item as any).href; + if (href) { + return getDirectoryFromPath(href); + } + + // Otherwise, get the directory from the first child item + const items = (item as any).items; + if (items && Array.isArray(items) && items.length > 0) { + return getPrimaryDirectory(items[0]); + } + } + + return ''; +}; + +/** + * Recursively sort items within a category based on directory order + */ +export const sortItemsByDirectoryOrder = ( + items: PropSidebarItem[], + directoryOrder: readonly string[], + exactMatch: boolean = false +): PropSidebarItem[] => { + return items + .map(item => { + // If this is a category, recursively sort its children + if (item.type === 'category') { + const childItems = (item as any).items; + if (childItems && Array.isArray(childItems)) { + return { + ...item, + items: sortItemsByDirectoryOrder(childItems, directoryOrder, exactMatch) + }; + } + } + return item; + }) + .sort((a, b) => { + const aPrimaryDir = getPrimaryDirectory(a); + const bPrimaryDir = getPrimaryDirectory(b); + + let aIndex = -1; + let bIndex = -1; + + if (exactMatch) { + aIndex = directoryOrder.indexOf(aPrimaryDir); + bIndex = directoryOrder.indexOf(bPrimaryDir); + } else { + // For non-exact match, find the first directory that contains the pattern + aIndex = directoryOrder.findIndex(dir => aPrimaryDir.includes(dir)); + bIndex = directoryOrder.findIndex(dir => bPrimaryDir.includes(dir)); + } + + // Items not found in the directory order go to the end + if (aIndex === -1 && bIndex === -1) return 0; + if (aIndex === -1) return 1; + if (bIndex === -1) return -1; + + return aIndex - bIndex; + }); +}; + /** * Check if a sidebar item matches any of the given patterns */ @@ -84,11 +159,9 @@ export const getItemsForCategory = ( exactMatch: boolean = false ): PropSidebarItem[] => { if (!sidebar || !Array.isArray(sidebar)) return []; - const matchedItems = sidebar.filter((item: PropSidebarItem) => { return itemMatchesPattern(item, categoryConfig, exactMatch); }); - return matchedItems; }; @@ -96,11 +169,18 @@ export const getItemsForCategory = ( * Build categories from the configuration */ export const buildCategories = (sidebar: PropSidebarItem[]): Category[] => { - return Object.entries(CATEGORY_CONFIG).map(([id, config]) => ({ - id, - title: config.title, - items: getItemsForCategory(sidebar, config.directories, config.exactMatch), - })); + return Object.entries(CATEGORY_CONFIG).map(([id, config]) => { + const matchedItems = getItemsForCategory(sidebar, config.directories, config.exactMatch); + + // Sort items recursively by directory order + const sortedItems = sortItemsByDirectoryOrder(matchedItems, config.directories, config.exactMatch); + + return { + id, + title: config.title, + items: sortedItems, + }; + }); }; /** From f43c046eb511a825d9742aefbd0bbab62202483d Mon Sep 17 00:00:00 2001 From: Rob Dominguez Date: Fri, 27 Jun 2025 13:52:16 -0700 Subject: [PATCH 5/5] Update titles and order Updates titles and order of pages in the Getting Started section. --- docs/automation.mdx | 2 +- docs/capabilities.mdx | 4 ++-- docs/decision-making.mdx | 2 +- docs/how-to-talk-to-promptql.mdx | 4 ++-- src/theme/DocSidebar/categories.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/automation.mdx b/docs/automation.mdx index f431e15b..a69f2c94 100644 --- a/docs/automation.mdx +++ b/docs/automation.mdx @@ -1,6 +1,6 @@ --- sidebar_position: 1.8 -sidebar_label: Automation +sidebar_label: Build Automations description: "Learn how to build automated workflows and processes with PromptQL for reliable, repeatable business tasks." keywords: diff --git a/docs/capabilities.mdx b/docs/capabilities.mdx index c86f08ac..f0c75073 100644 --- a/docs/capabilities.mdx +++ b/docs/capabilities.mdx @@ -1,6 +1,6 @@ --- -sidebar_position: 1.6 -sidebar_label: What can PromptQL do? +sidebar_position: 1.5 +sidebar_label: Capabilities description: "Learn what PromptQL can do to help you make decisions quicker or automate tasks with relability and accuracy." keywords: diff --git a/docs/decision-making.mdx b/docs/decision-making.mdx index 0ce944fa..1dc8c7cc 100644 --- a/docs/decision-making.mdx +++ b/docs/decision-making.mdx @@ -1,6 +1,6 @@ --- sidebar_position: 1.7 -sidebar_label: Decision Making +sidebar_label: Make Decisions description: "Learn how you can use PrompQL for accurate AI in your decision-making processes." keywords: - promptql diff --git a/docs/how-to-talk-to-promptql.mdx b/docs/how-to-talk-to-promptql.mdx index f7a10d67..35693505 100644 --- a/docs/how-to-talk-to-promptql.mdx +++ b/docs/how-to-talk-to-promptql.mdx @@ -1,6 +1,6 @@ --- -sidebar_position: 1.5 -sidebar_label: How to talk to PromptQL +sidebar_position: 1.6 +sidebar_label: Talk to PromptQL description: "Learn how to talk with your data via PromptQL." keywords: - promptql diff --git a/src/theme/DocSidebar/categories.ts b/src/theme/DocSidebar/categories.ts index d8dce87f..d566c812 100644 --- a/src/theme/DocSidebar/categories.ts +++ b/src/theme/DocSidebar/categories.ts @@ -7,7 +7,7 @@ type CategoryConfig = { export const CATEGORY_CONFIG: Record = { gettingStarted: { title: 'Getting Started', - directories: ['quickstart', 'how-to-talk-to-promptql', 'capabilities', 'decision-making', 'automation'], + directories: ['quickstart', 'capabilities', 'how-to-talk-to-promptql', 'decision-making', 'automation'], exactMatch: true, }, coreConcepts: {