diff --git a/src/components/SideMenu/MenuItems.tsx b/src/components/SideMenu/MenuItems.tsx index af9ab197..142aa0e1 100644 --- a/src/components/SideMenu/MenuItems.tsx +++ b/src/components/SideMenu/MenuItems.tsx @@ -26,6 +26,7 @@ export class MenuItems extends React.Component { className={className} style={this.props.style} $expanded={expanded} + root={root || false} {...(root ? { role: 'menu' } : {})} > {items.map((item, idx) => ( diff --git a/src/components/SideMenu/styled.elements.ts b/src/components/SideMenu/styled.elements.ts index 33de76ab..3d5249d3 100644 --- a/src/components/SideMenu/styled.elements.ts +++ b/src/components/SideMenu/styled.elements.ts @@ -84,7 +84,7 @@ function menuItemActive( } } -export const MenuItemUl = styled.ul<{ $expanded: boolean }>` +export const MenuItemUl = styled.ul<{ $expanded: boolean; root: boolean }>` margin: 0; padding: 0; @@ -97,6 +97,8 @@ export const MenuItemUl = styled.ul<{ $expanded: boolean }>` } ${props => (props.$expanded ? '' : 'display: none;')}; + ${props => (props.root ? '' : 'margin-left: 8px;')}; + ${props => (props.root ? '' : 'border-left: 1px solid lightblue;')} `; export const MenuItemLi = styled.li<{ depth: number }>` diff --git a/src/services/MenuBuilder.ts b/src/services/MenuBuilder.ts index 4ad28f7b..bbf7bb9c 100644 --- a/src/services/MenuBuilder.ts +++ b/src/services/MenuBuilder.ts @@ -28,7 +28,41 @@ export class MenuBuilder { } else { items.push(...MenuBuilder.getTagsItems(parser, tagsMap, undefined, undefined, options)); } - return items; + + if (options.sideNavLayout !== 'factored') return items; + + return MenuBuilder.factorByPrefix(items); + } + + static factorByPrefix(items: ContentItemModel[]): ContentItemModel[] { + const newItems: ContentItemModel[] = []; + let newChildren: ContentItemModel[] = []; + let parent: GroupModel | null = null; + let prefix = ''; + for (const item of items) { + if (parent && item.name.startsWith(prefix)) { + item.parent = parent; + item.depth = parent.depth + 1; + newChildren.push(item); + } else { + if (newChildren.length > 0) { + for (const child of MenuBuilder.factorByPrefix(newChildren)) { + if (child.sidebarLabel.startsWith(prefix)) { + child.sidebarLabel = '…' + child.sidebarLabel.slice(prefix.length - 1); + } + parent!.items.push(child); + } + newChildren = []; + } + + newItems.push(item); + if (item instanceof GroupModel) { + parent = item; + prefix = item.name + '/'; + } else parent = null; + } + } + return newItems; } /** diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 0cdd7f9e..ade97680 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -27,6 +27,7 @@ export interface RedocRawOptions { onlyRequiredInSamples?: boolean | string; showExtensions?: boolean | string | string[]; sideNavStyle?: SideNavStyleEnum; + sideNavLayout?: 'default' | 'factored'; hideSingleRequestSampleTab?: boolean | string; hideRequestPayloadSample?: boolean; menuToggle?: boolean | string; @@ -180,6 +181,10 @@ export class RedocNormalizedOptions { } } + static normalizeSideNavLayout(value: RedocRawOptions['sideNavLayout']): 'default' | 'factored' { + return value === 'factored' ? value : 'default'; + } + static normalizePayloadSampleIdx(value: RedocRawOptions['payloadSampleIdx']): number { if (typeof value === 'number') { return Math.max(0, value); // always greater or equal than 0 @@ -231,6 +236,7 @@ export class RedocNormalizedOptions { onlyRequiredInSamples: boolean; showExtensions: boolean | string[]; sideNavStyle: SideNavStyleEnum; + sideNavLayout: 'default' | 'factored'; hideSingleRequestSampleTab: boolean; hideRequestPayloadSample: boolean; menuToggle: boolean; @@ -303,6 +309,7 @@ export class RedocNormalizedOptions { this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples); this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions); this.sideNavStyle = RedocNormalizedOptions.normalizeSideNavStyle(raw.sideNavStyle); + this.sideNavLayout = RedocNormalizedOptions.normalizeSideNavLayout(raw.sideNavLayout); this.hideSingleRequestSampleTab = argValueToBoolean(raw.hideSingleRequestSampleTab); this.hideRequestPayloadSample = argValueToBoolean(raw.hideRequestPayloadSample); this.menuToggle = argValueToBoolean(raw.menuToggle, true);