diff --git a/src/components/sidebar/collapsibleSidebarLink.tsx b/src/components/sidebar/collapsibleSidebarLink.tsx index e660102a6f2cf..73c51680888ab 100644 --- a/src/components/sidebar/collapsibleSidebarLink.tsx +++ b/src/components/sidebar/collapsibleSidebarLink.tsx @@ -16,6 +16,12 @@ interface SidebarLinkProps { */ title: string; to: string; + + /** + * Shows a beta badge next to the title + */ + beta?: boolean; + /** * Children represent the additional links nested under this sidebar link */ @@ -26,6 +32,11 @@ interface SidebarLinkProps { * Indicates that the links are currently hidden. Overridden by isActive */ collapsed?: boolean | null; + + /** + * Shows a new badge next to the title + */ + isNew?: boolean; } /** @@ -39,6 +50,8 @@ export function CollapsibleSidebarLink({ path, collapsed = null, className = '', + beta = false, + isNew = false, }: SidebarLinkProps) { const isActive = path?.indexOf(to) === 0; const enableSubtree = isActive || collapsed === false; @@ -54,6 +67,8 @@ export function CollapsibleSidebarLink({ isActive={to === getUnversionedPath(path)} collapsible={hasSubtree} title={title} + beta={beta} + isNew={isNew} onClick={() => { // Allow toggling the sidebar subtree only if the item is selected if (path === to) { diff --git a/src/components/sidebar/dynamicNav.tsx b/src/components/sidebar/dynamicNav.tsx index bf144120f871b..8f0a2841d5a7f 100644 --- a/src/components/sidebar/dynamicNav.tsx +++ b/src/components/sidebar/dynamicNav.tsx @@ -11,6 +11,8 @@ type Node = { [key: string]: any; context: { [key: string]: any; + beta?: boolean; + new?: boolean; sidebar_hidden?: boolean; sidebar_order?: number; sidebar_title?: string; @@ -63,7 +65,7 @@ export const renderChildren = ( showDepth: number = 0, depth: number = 0 ): React.ReactNode[] => { - return sortPages( + const sortedChildren = sortPages( children.filter( ({name, node}) => node && @@ -73,23 +75,32 @@ export const renderChildren = ( !node.context.sidebar_hidden ), ({node}) => node! - ).map(({node, children: nodeChildren}) => { + ); + + const result: React.ReactNode[] = []; + + sortedChildren.forEach(({node, children: nodeChildren}) => { // will not be null because of the filter above if (!node) { - return null; + return; } - return ( + + result.push( = showDepth} path={path} + beta={node.context.beta} + isNew={node.context.new} > {renderChildren(nodeChildren, exclude, path, showDepth, depth + 1)} ); }); + + return result; }; type ChildrenProps = { diff --git a/src/components/sidebar/platformSidebar.tsx b/src/components/sidebar/platformSidebar.tsx index 8e6213a6d91e0..ebbe1694ec378 100644 --- a/src/components/sidebar/platformSidebar.tsx +++ b/src/components/sidebar/platformSidebar.tsx @@ -22,6 +22,8 @@ export function PlatformSidebar({ sidebar_order: n.frontmatter.sidebar_order, sidebar_title: n.frontmatter.sidebar_title, sidebar_hidden: n.frontmatter.sidebar_hidden, + beta: n.frontmatter.beta, + new: n.frontmatter.new, }, path: '/' + n.path + '/', }; diff --git a/src/components/sidebar/sidebarLink.tsx b/src/components/sidebar/sidebarLink.tsx index 10b50564545b0..e5de1be487543 100644 --- a/src/components/sidebar/sidebarLink.tsx +++ b/src/components/sidebar/sidebarLink.tsx @@ -13,11 +13,15 @@ export function SidebarLink({ collapsible, onClick, topLevel = false, + beta = false, + isNew = false, }: { href: string; title: string; + beta?: boolean; collapsible?: boolean; isActive?: boolean; + isNew?: boolean; onClick?: () => void; topLevel?: boolean; }) { @@ -33,12 +37,16 @@ export function SidebarLink({ }`} data-sidebar-link > -
{title}
+
+ {title} + {beta && BETA} + {isNew && NEW} +
{collapsible && } ); } export function SidebarSeparator() { - return
; + return
; } diff --git a/src/components/sidebar/style.module.scss b/src/components/sidebar/style.module.scss index 3a6850e7d3d31..91c1894602782 100644 --- a/src/components/sidebar/style.module.scss +++ b/src/components/sidebar/style.module.scss @@ -91,10 +91,6 @@ } } - .sidebar-separator { - border-top: 1px solid var(--border-color); - } - .toc { font-size: 0.875rem; flex: 1; @@ -159,3 +155,57 @@ background-color: var(--brandDecoration); } } + +.sidebar-link-content { + display: flex; + align-items: center; + gap: 0.5rem; + flex: 1; + min-width: 0; +} + +.beta-badge { + display: inline-flex; + align-items: center; + padding: 0.0625rem 0.375rem; + font-size: 0.625rem; + font-weight: 500; + letter-spacing: 0.02em; + color: #fafaf9; /* off-white */ + background-color: transparent; + border: 1px solid #f59e0b; /* amber-500 warning color */ + border-radius: 0.25rem; + white-space: nowrap; + flex-shrink: 0; +} + +:global(.dark) .beta-badge { + color: #fafaf9; /* off-white */ + border-color: #fbbf24; /* amber-400 for dark mode */ +} + +.new-badge { + display: inline-flex; + align-items: center; + padding: 0.0625rem 0.375rem; + font-size: 0.625rem; + font-weight: 500; + letter-spacing: 0.02em; + color: #fafaf9; /* off-white */ + background-color: transparent; + border: 1px solid #10b981; /* emerald-500 success green */ + border-radius: 0.25rem; + white-space: nowrap; + flex-shrink: 0; +} + +:global(.dark) .new-badge { + color: #fafaf9; /* off-white */ + border-color: #34d399; /* emerald-400 for dark mode */ +} + +.sidebar-separator { + margin: 1rem 0; + border: none; + border-top: 1px solid var(--border-color); +} diff --git a/src/types/frontmatter.ts b/src/types/frontmatter.ts index 197a4e2c5a8b3..c7a71bbb5041d 100644 --- a/src/types/frontmatter.ts +++ b/src/types/frontmatter.ts @@ -13,24 +13,36 @@ export interface FrontMatter { * Document title - used in as well as things like search titles. */ title: string; + /** + * Set this to true to show a "beta" badge next to the title in the sidebar + */ + beta?: boolean; /** * A description to use in the <meta> header, as well as in auto generated page grids. */ customCanonicalTag?: string; + /** Add this if you want to add a canonical tag (without this it will default to the page url). Should be a relative path without the domain (e.g. `/platforms/react/options/`) */ description?: string; + /** * Set this to true to mark this page as a draft, and hide it from various other components (such as the PageGrid). */ draft?: boolean; + /** * Set this to true to take all the available width for the page content. */ fullWidth?: boolean; + /** * A list of keywords for indexing with search. */ keywords?: string[]; + /** + * Set this to true to show a "new" badge next to the title in the sidebar + */ + new?: boolean; /** * The next page in the bottom pagination navigation. @@ -69,6 +81,11 @@ export interface FrontMatter { */ previousPage?: PaginationNavNode; + /** + * Set this to true to show a separator/divider below this item in the sidebar + */ + section_end_divider?: boolean; + /** * The next page in the sidebar navigation. */